diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da84c08ac3d..d793bdef16a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -780,6 +780,31 @@ jobs: spec: cypress/e2e/Cypress-System/login.spec.js working-directory: ${{env.SUBMITTY_REPOSITORY}}/site browser: chrome + + - name: Get API token + id: get_token + run: | + RESPONSE=$(curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "user_id=instructor&password=instructor" http://localhost/api/token) + echo "$RESPONSE" + status=$(echo "$RESPONSE" | jq -r '.status') + if [ "$status" = "fail" ]; then + exit 1 + fi + TOKEN=$(echo "$RESPONSE" | jq -r '.data.token') + echo "API_TOKEN=$TOKEN" >> $GITHUB_ENV + + - name: Create Homework + run: | + cd .setup/ansible + ansible-playbook --private-key /home/runner/.ssh/id_rsa -e "ansible_user=runner submitty_homework_creation_api_url=http://localhost/api/term/course/upload submitty_homework_creation_api_key=${{ env.API_TOKEN }}" -i inventory/submitty playbooks/submitty_homework_creation.yml + + - name: Run Ansible cypress test + uses: cypress-io/github-action@v6 + with: + config: baseUrl=http://localhost + spec: cypress/e2e/Cypress-Ansible/ansible-course.spec.js + working-directory: ${{env.SUBMITTY_REPOSITORY}}/site + browser: chrome - name: Setup HTTP/2 run: | @@ -955,7 +980,7 @@ jobs: ssh -T localhost sudo systemctl start postgresql - - name: Run ansible script + - name: Run Ansible scripts shell: bash run: | cd .setup/ansible @@ -968,6 +993,10 @@ jobs: sudo chmod -R a+rwx ${SUBMITTY_INSTALL_DIR} sudo chmod -R a+rwx /tmp/ + - name: Validate image + run: | + curl --show-error --fail --include http://localhost/authentication/login + - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} @@ -984,10 +1013,28 @@ jobs: run: | npm ci - - name: Validate image + - name: Get API token + id: get_token run: | - curl --show-error --fail --include http://localhost/authentication/login + RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d '{"user_id": "instructor", "password": "instructor"}' http://localhost/api/token) + echo "$RESPONSE" + status=$(echo "$RESPONSE" | jq -r '.status') + if [ "$status" = "fail" ]; then + exit 1 + fi + TOKEN=$(echo "$RESPONSE" | jq -r '.data.token') + echo "API_TOKEN=$TOKEN" >> $GITHUB_ENV + + - name: Debug env + if: failure() + run: | + echo ${{ env.API_TOKEN }} + - name: Create Homework + run: | + cd .setup/ansible + ansible-playbook --private-key /home/runner/.ssh/id_rsa -e "ansible_user=runner submitty_homework_creation_api_url=http://localhost/api/term/course/upload submitty_homework_creation_api_key=${{ env.API_TOKEN }}" -i inventory/submitty playbooks/submitty_homework_creation.yml + - name: Run Ansible cypress test uses: cypress-io/github-action@v6 with: diff --git a/.setup/ansible/playbooks/submitty_course_creation.yml b/.setup/ansible/playbooks/submitty_course_creation.yml index 643dae3be20..ba9a4031a34 100644 --- a/.setup/ansible/playbooks/submitty_course_creation.yml +++ b/.setup/ansible/playbooks/submitty_course_creation.yml @@ -1,6 +1,7 @@ --- - name: Submitty Course Creation hosts: submitty + remote_user: runner become: true vars: @@ -49,7 +50,7 @@ submitty_course_creation_lastname: lastname submitty_course_creation_email: email - - name: Add Instructors + - name: Add Instructor ansible.builtin.include_role: name: submitty_course_add_instructor vars: @@ -61,7 +62,47 @@ submitty_course_add_instructor_email: email submitty_course_add_instructor_password: instructor2 + - name: Add runner + ansible.builtin.include_role: + name: submitty_course_add_instructor + vars: + submitty_course_add_instructor_term: term + submitty_course_add_instructor_course: course + submitty_course_add_instructor_username: runner + submitty_course_add_instructor_firstname: firstname + submitty_course_add_instructor_lastname: lastname + submitty_course_add_instructor_email: email + submitty_course_add_instructor_password: runner + - name: Add Users + ansible.builtin.include_role: + name: submitty_add_user + vars: + submitty_add_user_username: "{{ user.username }}" + submitty_add_user_firstname: "{{ user.firstname }}" + submitty_add_user_lastname: "{{ user.lastname }}" + submitty_add_user_email: "{{ user.email }}" + submitty_add_user_password: "{{ user.password }}" + loop: + - username: aphacker + firstname: firstname + lastname: lastname + email: email + password: aphacker + - username: bitdiddle + firstname: firstname + lastname: lastname + email: email + password: bitdiddle + - username: adamsg + firstname: firstname + lastname: lastname + email: email + password: adamsg + loop_control: + loop_var: user + + - name: Add users to course ansible.builtin.include_role: name: submitty_course_add_user vars: diff --git a/.setup/ansible/playbooks/submitty_homework_creation.yml b/.setup/ansible/playbooks/submitty_homework_creation.yml new file mode 100644 index 00000000000..321b6b6f6eb --- /dev/null +++ b/.setup/ansible/playbooks/submitty_homework_creation.yml @@ -0,0 +1,22 @@ +--- +- name: Submitty Homework Creation + hosts: localhost + remote_user: instructor + connection: local + + tasks: + # This role is defined in the Submitty Repo under .setup/ansible/roles + - name: Create gradeable + ansible.builtin.include_role: + name: submitty_homework_creation + + vars_prompt: + - name: submitty_homework_creation_api_url + prompt: Enter the full API url (http://{domain.com}/api/{semester}/{course}/upload) + private: no + - name: submitty_homework_creation_passed_api_key + prompt: Enter your API key + private: yes + - name: submitty_homework_creation_password + prompt: Enter your password + private: yes diff --git a/.setup/ansible/playbooks/submitty_install.yml b/.setup/ansible/playbooks/submitty_install.yml index e28931c1119..38c5af2c1f2 100644 --- a/.setup/ansible/playbooks/submitty_install.yml +++ b/.setup/ansible/playbooks/submitty_install.yml @@ -4,7 +4,7 @@ - name: Install Submitty hosts: submitty - remote_user: ubuntu + remote_user: runner become: true tasks: - name: Install Submitty diff --git a/.setup/ansible/roles/submitty_add_user/tasks/main.yml b/.setup/ansible/roles/submitty_add_user/tasks/main.yml index 7a28eacc665..3a7422ad3d4 100644 --- a/.setup/ansible/roles/submitty_add_user/tasks/main.yml +++ b/.setup/ansible/roles/submitty_add_user/tasks/main.yml @@ -1,11 +1,11 @@ --- - name: Add student to submitty users. ansible.builtin.expect: - command: ./adduser.py "{{ submitty_add_user_username }}" + command: "./adduser.py {{ submitty_add_user_username }}" chdir: /usr/local/submitty/sbin/ echo: true responses: - (?m)^User givenname: "{{ submitty_add_user_username }}" + (?m)^User givenname: "{{ submitty_add_user_firstname }}" (?m)^User preferred name: "{{ submitty_add_user_firstname }}" (?m)^User familyname: "{{ submitty_add_user_lastname }}" (?m)^User email: "{{ submitty_add_user_email }}" diff --git a/.setup/ansible/roles/submitty_course_add_user/tasks/main.yml b/.setup/ansible/roles/submitty_course_add_user/tasks/main.yml index b14c7f56317..887e4658186 100644 --- a/.setup/ansible/roles/submitty_course_add_user/tasks/main.yml +++ b/.setup/ansible/roles/submitty_course_add_user/tasks/main.yml @@ -1,20 +1,5 @@ --- # Assumes that local user accounts already exist. - -- name: Add student to submitty users. - ansible.builtin.expect: - command: ./adduser.py "{{ submitty_course_add_user_username }}" - chdir: /usr/local/submitty/sbin/ - echo: true - responses: - (?m)^User givenname: "{{ submitty_course_add_user_username }}" - (?m)^User preferred name: "{{ submitty_course_add_user_firstname }}" - (?m)^User familyname: "{{ submitty_course_add_user_lastname }}" - (?m)^User email: "{{ submitty_course_add_user_email }}" - (?m)^User password: "{{ submitty_course_add_user_password }}" - become: true - become_user: root - - name: Add existing students to the course database. ansible.builtin.command: cmd: ./adduser_course.py "{{ submitty_course_add_user_username }}" diff --git a/.setup/ansible/roles/submitty_course_creation/tasks/main.yml b/.setup/ansible/roles/submitty_course_creation/tasks/main.yml index 2707dbf2c29..172ee0b05cd 100644 --- a/.setup/ansible/roles/submitty_course_creation/tasks/main.yml +++ b/.setup/ansible/roles/submitty_course_creation/tasks/main.yml @@ -15,6 +15,20 @@ vars: course_name: "{{ submitty_course_creation_course }}" +- name: Add instructors to submitty users. + ansible.builtin.expect: + command: ./adduser.py "{{ submitty_course_creation_username }}" + chdir: /usr/local/submitty/sbin/ + echo: true + responses: + (?m)^User givenname: "{{ submitty_course_creation_firstname }}" + (?m)^User preferred name: "{{ submitty_course_creation_firstname }}" + (?m)^User familyname: "{{ submitty_course_creation_lastname }}" + (?m)^User email: "{{ submitty_course_creation_email }}" + (?m)^User password: NotUsedWithLdap + become: true + become_user: root + - name: Add instructors to the course groups. ansible.builtin.user: name: "{{ submitty_course_creation_username }}" @@ -77,20 +91,6 @@ recurse: true mode: g+s -- name: Add instructors to submitty users. - ansible.builtin.expect: - command: ./adduser.py "{{ submitty_course_creation_username }}" - chdir: /usr/local/submitty/sbin/ - echo: true - responses: - (?m)^User givenname: "{{ submitty_course_creation_firstname }}" - (?m)^User preferred name: "{{ submitty_course_creation_firstname }}" - (?m)^User familyname: "{{ submitty_course_creation_lastname }}" - (?m)^User email: "{{ submitty_course_creation_email }}" - (?m)^User password: NotUsedWithLdap - become: true - become_user: root - # Prepare the course directory and course database. # Assumes top-level Submitty database exists. diff --git a/.setup/ansible/roles/submitty_homework_creation/files/config_files/file_homework.json b/.setup/ansible/roles/submitty_homework_creation/files/config_files/file_homework.json new file mode 100644 index 00000000000..9f7ec77f51b --- /dev/null +++ b/.setup/ansible/roles/submitty_homework_creation/files/config_files/file_homework.json @@ -0,0 +1,162 @@ +{ + "title": "Homework from file", + "type": "Electronic File", + "id": "file_homework", + "instructions_url": "", + "syllabus_bucket": "homework", + "autograding_config_path": "\/usr\/local\/submitty\/more_autograding_examples\/python_simple_homework\/config", + "bulk_upload": false, + "ta_grading": "true", + "grade_inquiries": "true", + "dates": { + "ta_view_start_date": "1970-01-01 23:59:59", + "grade_start_date": "9997-12-31 23:59:59", + "grade_due_date": "9998-12-31 23:59:59", + "grade_released_date": "9998-12-31 23:59:59", + "team_lock_date": "1971-01-01 23:59:59", + "submission_open_date": "1971-01-01 23:59:59", + "submission_due_date": "9996-12-31 23:59:59", + "grade_inquiry_start_date": "9999-01-01 23:59:59", + "grade_inquiry_due_date": "9999-01-06 23:59:59", + "has_due_date": true, + "has_release_date": true, + "late_submission_allowed": true, + "late_days": 0 + }, + "rubric": [ + { + "title": "Read Me", + "ta_comment": "Reward student for including docstrings.", + "student_comment": "Code should be organized into logical and intuitive functions.", + "lower_clamp": 0, + "default": 2, + "max_value": 2, + "upper_clamp": 2, + "text": false, + "peer_component": false, + "page": 0, + "is_itempool_linked": false, + "itempool": "", + "marks": [ + { + "points": 0, + "title": "Full Credit", + "publish": false + }, + { + "points": -1, + "title": "Minor errors in Read Me", + "publish": false + }, + { + "points": -2, + "title": "Major errors in Read Me or Read Me missing", + "publish": false + } + ] + }, + { + "title": "Coding Style", + "ta_comment": "Deduct points if the student uses global variables.", + "student_comment": "The use of builtin functions is prohibited for this exercise.", + "lower_clamp": 0, + "default": 5, + "max_value": 5, + "upper_clamp": 5, + "text": false, + "peer_component": false, + "page": 0, + "is_itempool_linked": false, + "itempool": "", + "marks": [ + { + "points": 0, + "title": "Full Credit", + "publish": false + }, + { + "points": -5, + "title": "Code is unreadable", + "publish": false + }, + { + "points": -3, + "title": "Code is very difficult to understand", + "publish": false + }, + { + "points": -1, + "title": "Code is difficult to understand", + "publish": false + } + ] + }, + { + "title": "Documentation", + "ta_comment": "Reward student for properly using divide and conquer in code.", + "student_comment": "Code should be broken down into functions that each solve a part of the problem.", + "lower_clamp": 0, + "default": 5, + "max_value": 5, + "upper_clamp": 5, + "text": false, + "peer_component": false, + "page": 0, + "is_itempool_linked": false, + "itempool": "", + "marks": [ + { + "points": 0, + "title": "Full Credit", + "publish": false + }, + { + "points": -5, + "title": "No documentation", + "publish": false + }, + { + "points": -3, + "title": "Very little documentation or documentation makes no sense", + "publish": false + }, + { + "points": -1, + "title": "Way too much documentation and\/or documentation makes no sense", + "publish": false + } + ] + }, + { + "title": "Extra Credit", + "ta_comment": "Deduct points if the student uses global variables.", + "student_comment": "A switch case should be used and not the if-else structure.", + "lower_clamp": 0, + "default": 0, + "max_value": 0, + "upper_clamp": 5, + "text": false, + "peer_component": false, + "page": 0, + "is_itempool_linked": false, + "itempool": "", + "marks": [ + { + "points": 0, + "title": "No Credit", + "publish": false + }, + { + "points": 2, + "title": "Extra credit done poorly", + "publish": false + }, + { + "points": 5, + "title": "Extra credit is acceptable", + "publish": false + } + ] + } + ] +} \ No newline at end of file diff --git a/.setup/ansible/roles/submitty_homework_creation/files/json_list.txt b/.setup/ansible/roles/submitty_homework_creation/files/json_list.txt new file mode 100644 index 00000000000..c2da9456261 --- /dev/null +++ b/.setup/ansible/roles/submitty_homework_creation/files/json_list.txt @@ -0,0 +1 @@ +file_homework.json \ No newline at end of file diff --git a/.setup/ansible/roles/submitty_homework_creation/tasks/create_gradeable.yml b/.setup/ansible/roles/submitty_homework_creation/tasks/create_gradeable.yml new file mode 100644 index 00000000000..b6b73d46a16 --- /dev/null +++ b/.setup/ansible/roles/submitty_homework_creation/tasks/create_gradeable.yml @@ -0,0 +1,16 @@ +- name: Create gradeables with API + ansible.builtin.uri: + url: "{{ submitty_homework_creation_api_url }}" + method: POST + body_format: json + body: "{{ lookup('file', 'config_files/' + submitty_homework_creation_json_file) }}" + return_content: yes + headers: + Authorization: "{{ submitty_homework_creation_api_key }}" + register: submitty_homework_creation_api_response + +- name: Fail when status is error + ansible.builtin.fail: + msg: "{{ (submitty_homework_creation_api_response.content | from_json).message }}" + when: + - (submitty_homework_creation_api_response.content | from_json).status != 'success' diff --git a/.setup/ansible/roles/submitty_homework_creation/tasks/main.yml b/.setup/ansible/roles/submitty_homework_creation/tasks/main.yml new file mode 100644 index 00000000000..ef089fb49ef --- /dev/null +++ b/.setup/ansible/roles/submitty_homework_creation/tasks/main.yml @@ -0,0 +1,25 @@ +- name: Read JSON content from a file + ansible.builtin.set_fact: + var_json_list: "config_files/{{ lookup('file', 'json_list.txt') | split('\n') }}" + +- name: Get API key + ansible.builtin.uri: + url: "http://localhost/api/token" + method: POST + body_format: json + body: { + "user_id": "{{ submitty_homework_creation_user_id }}", + "password": "{{ submitty_homework_creation_password }}" + } + return_content: yes + register: submitty_homework_creation_api_response + +- name: Use task list to loop through gradeables + ansible.builtin.include_tasks: + file: create_gradeable.yml + loop: "{{ submitty_homework_creation_json_list }}" + loop_control: + loop_var: submitty_homework_creation_json_file + vars: + submitty_homework_creation_json_file: "{{ submitty_homework_creation_json_file }}" + submitty_homework_creation_api_key: "{{ submitty_homework_creation_passed_api_key }}" diff --git a/.setup/ansible/roles/submitty_install/defaults/main.yml b/.setup/ansible/roles/submitty_install/defaults/main.yml index b70adff3b76..232f24d7c05 100644 --- a/.setup/ansible/roles/submitty_install/defaults/main.yml +++ b/.setup/ansible/roles/submitty_install/defaults/main.yml @@ -16,7 +16,7 @@ submitty_install_self_account_creation: n submitty_install_sysadmin_email: sysadmin@localhost submitty_install_submitty_email: submitty@localhost submitty_install_institution_url: localhost -submitty_install_authentication_method: 1 +submitty_install_authentication_method: 2 submitty_install_email_enabled: y submitty_install_ssl_enabled: false submitty_install_admin_email: admin@localhost diff --git a/.setup/ansible/roles/submitty_install/tasks/main.yml b/.setup/ansible/roles/submitty_install/tasks/main.yml index ebd3288cdfe..a2103890d64 100644 --- a/.setup/ansible/roles/submitty_install/tasks/main.yml +++ b/.setup/ansible/roles/submitty_install/tasks/main.yml @@ -47,11 +47,11 @@ {{ submitty_install_vcs_url }} {{ submitty_install_institution_name }} {{ submitty_install_institution_url }} - {{ submitty_install_self_account_creation }} {{ submitty_install_sysadmin_email }} {{ submitty_install_submitty_email }} {{ submitty_install_institution_url }} {{ submitty_install_authentication_method }} + {{ submitty_install_self_account_creation }} {{ submitty_install_admin_email }} {{ submitty_install_email_enabled }} {{ submitty_install_admin_email }} diff --git a/sbin/adduser.py b/sbin/adduser.py index 349c04465ef..d88b490efe9 100644 --- a/sbin/adduser.py +++ b/sbin/adduser.py @@ -113,7 +113,7 @@ def main(): while AUTHENTICATION_METHOD == 'DatabaseAuthentication': password = input('User password{}: '.format(extra)) if password != '': - update['user_password'] = get_php_db_password(password) + update_data['user_password'] = get_php_db_password(password) break elif user is not None and password == '': break diff --git a/site/cypress/e2e/Cypress-Ansible/ansible-course.spec.js b/site/cypress/e2e/Cypress-Ansible/ansible-course.spec.js index 9b40263e7f7..e2afb1b8812 100644 --- a/site/cypress/e2e/Cypress-Ansible/ansible-course.spec.js +++ b/site/cypress/e2e/Cypress-Ansible/ansible-course.spec.js @@ -1,6 +1,7 @@ +import { getApiKey } from '../../support/utils'; describe('Testing website when created by ansible scripts', () => { it('Should be able to login and see the course', () => { cy.login('instructor'); - cy.visit('term', 'course'); + cy.visit('term/course'); }); });