diff --git a/Dockerfile-api b/Dockerfile-api index ab8b022..ca40107 100644 --- a/Dockerfile-api +++ b/Dockerfile-api @@ -25,7 +25,10 @@ RUN mkdir -p /var/tmp && cd /BASIL-API/db/models && \ # Apply patches RUN patch -u /usr/local/lib/python3.12/site-packages/tmt/steps/provision/podman.py < /BASIL-API/misc/tmt_provision_podman.patch +# Remove BASIL ADMIN PASSWORD from the environment ENV BASIL_ADMIN_PASSWORD= + +EXPOSE ${BASIL_API_PORT} CMD echo "BASIL_API_PORT: ${BASIL_API_PORT}" && cd api && \ gunicorn --access-logfile /var/tmp/tc-gunicorn-access.log \ --error-logfile /var/tmp/tc-gunicorn-error.log \ diff --git a/Dockerfile-app b/Dockerfile-app index 247465a..dfe744f 100644 --- a/Dockerfile-app +++ b/Dockerfile-app @@ -17,4 +17,5 @@ RUN npm run build # RUN sed -i "s/src=\"\//src=\"/g" dist/index.html # RUN sed -i "s/href=\"\//href=\"/g" dist/index.html +EXPOSE ${APP_PORT} CMD ["npm", "run", "start"] diff --git a/api/api.py b/api/api.py index 2308c5a..b5cdf86 100644 --- a/api/api.py +++ b/api/api.py @@ -5654,6 +5654,66 @@ def get(self): configs = [x.as_dict(full_data=True) for x in configs] return configs + def post(self): + request_data = request.get_json(force=True) + mandatory_fields = ['context_vars', 'environment_vars', + 'git_repo_ref', + 'provision_guest', 'provision_guest_port', 'provision_type', + 'ssh_key', 'title'] + if not check_fields_in_request(mandatory_fields, request_data): + return BAD_REQUEST_MESSAGE, BAD_REQUEST_STATUS + + dbi = db_orm.DbInterface(get_db()) + + # User + user = get_active_user_from_request(request_data, dbi.session) + if not isinstance(user, UserModel): + return UNAUTHORIZED_MESSAGE, UNAUTHORIZED_STATUS + + # Config + config_title = str(request_data['title']).strip() + git_repo_ref = str(request_data['git_repo_ref']).strip() + context_vars = str(request_data['context_vars']).strip() + environment_vars = str(request_data['environment_vars']).strip() + provision_type = str(request_data['provision_type']).strip() + provision_guest = str(request_data['provision_guest']).strip() + provision_guest_port = str(request_data['provision_guest_port']).strip() + ssh_key_id = request_data['ssh_key'] + + # Check mandatory fields + if config_title == '' or provision_type == '': + return BAD_REQUEST_MESSAGE, BAD_REQUEST_STATUS + + if provision_type == 'connect': + if provision_guest == '' or provision_guest_port == '' or ssh_key_id == '' or ssh_key_id == '0': + return BAD_REQUEST_MESSAGE, BAD_REQUEST_STATUS + + try: + ssh_key = dbi.session.query(SshKeyModel).filter( + SshKeyModel.id == ssh_key_id, + SshKeyModel.created_by_id == user.id + ).one() + except NoResultFound: + return BAD_REQUEST_MESSAGE, BAD_REQUEST_STATUS + else: + ssh_key = None + + test_config = TestRunConfigModel(config_title, + git_repo_ref, + context_vars, + environment_vars, + provision_type, + provision_guest, + provision_guest_port, + ssh_key, + user) + + dbi.session.add(test_config) + dbi.session.commit() + dbi.engine.dispose() + + return test_config.as_dict() + class TestRun(Resource): fields = get_model_editable_fields(TestRunModel, False) diff --git a/api/tmttestrun.py b/api/tmttestrun.py index 087f217..17ee42d 100644 --- a/api/tmttestrun.py +++ b/api/tmttestrun.py @@ -78,6 +78,10 @@ def __init__(self, id): sys.exit(3) env_str = f'basil_test_case_id={mapping.test_case.id};' + env_str += f'basil_test_case_title={mapping.test_case.title};' + env_str += f'basil_api_api={mapping.api.api};' + env_str += f'basil_api_library={mapping.api.library};' + env_str += f'basil_api_library_version={mapping.api.library_version};' env_str += f'basil_test_case_mapping_table={self.db_test_run.mapping_to};' env_str += f'basil_test_case_mapping_id={self.db_test_run.mapping_id};' env_str += f'basil_test_relative_path={mapping.test_case.relative_path};' diff --git a/app/src/app/AppLayout/AppLayout.tsx b/app/src/app/AppLayout/AppLayout.tsx index f49ea05..c09924a 100644 --- a/app/src/app/AppLayout/AppLayout.tsx +++ b/app/src/app/AppLayout/AppLayout.tsx @@ -84,14 +84,22 @@ const AppLayout: React.FunctionComponent = ({ children }) => { let url = Constants.API_BASE_URL + '/user/notifications?' url += '&user-id=' + auth.userId + '&token=' + auth.token - fetch(url) - .then((res) => res.json()) - .then((data) => { - setNotifications(data) - }) - .catch((err) => { - console.log(err.message) - }) + fetch(url).then((response) => { + const success = response.ok + response + .json() + .then((data) => { + console.log(data.message) + console.log(success) + if (!success) { + auth.logOut() + } + setNotifications(data) + }) + .catch((err) => { + console.log(err.message) + }) + }) } const Header = ( diff --git a/app/src/app/Constants/constants.tsx b/app/src/app/Constants/constants.tsx index 5f7f124..0fd6bea 100644 --- a/app/src/app/Constants/constants.tsx +++ b/app/src/app/Constants/constants.tsx @@ -160,6 +160,19 @@ export const docFormEmpty = { url: '' } +export const getLimitedText = (_text, _length) => { + if (_text == undefined) { + return '' + } + if (_length == 0) { + return _text + } + if (_text.length > _length) { + return _text.substr(0, _length) + '...' + } + return _text +} + export const logObject = (obj) => { let i let k diff --git a/app/src/app/Dashboard/APIListingTable.tsx b/app/src/app/Dashboard/APIListingTable.tsx index 35cbe66..6a4ae93 100644 --- a/app/src/app/Dashboard/APIListingTable.tsx +++ b/app/src/app/Dashboard/APIListingTable.tsx @@ -130,7 +130,7 @@ const APIListingTable: React.FunctionComponent = ({ {dataRow.api} - {dataRow.library_version} + {Constants.getLimitedText(dataRow.library_version, 10)} {dataRow.created_by} {dataRow.category} diff --git a/app/src/app/Mapping/MappingListingTable.tsx b/app/src/app/Mapping/MappingListingTable.tsx index b93fb97..eb9385c 100644 --- a/app/src/app/Mapping/MappingListingTable.tsx +++ b/app/src/app/Mapping/MappingListingTable.tsx @@ -112,20 +112,6 @@ const MappingListingTable: React.FunctionComponent = ( work_items: 'WORK ITEMS' } - const getLimitedText = (_text, _length) => { - if (_text == undefined) { - return '' - } - if (_length == 0) { - return _text - } - let tmp = _text.substr(0, _length) - if (_text.length > _length) { - tmp = tmp + '...' - } - return tmp - } - const coverageFormat = (x) => Number.parseFloat(x).toFixed(1) const getMappedSectionCodeBlockBackgroundColor = (snippet) => { @@ -277,7 +263,7 @@ const MappingListingTable: React.FunctionComponent = ( .then((data) => { if ('valid' in data) { ret = data['valid'] - let label = document.getElementById('label-document-valid-' + _id) + const label = document.getElementById('label-document-valid-' + _id) if (ret) { if (label) { label.innerHTML = ret @@ -372,9 +358,9 @@ const MappingListingTable: React.FunctionComponent = ( - {getLimitedText(test_case[Constants._TC_]['title'], 0)} + {Constants.getLimitedText(test_case[Constants._TC_]['title'], 0)} - {getLimitedText(test_case[Constants._TC_]['description'], 0)} + {Constants.getLimitedText(test_case[Constants._TC_]['description'], 0)} @@ -476,18 +462,18 @@ const MappingListingTable: React.FunctionComponent = ( - {getLimitedText(test_spec[Constants._TS_]['title'], 0)} + {Constants.getLimitedText(test_spec[Constants._TS_]['title'], 0)} Preconditions: - {getLimitedText(test_spec[Constants._TS_]['preconditions'], 0)} + {Constants.getLimitedText(test_spec[Constants._TS_]['preconditions'], 0)} Test Description: - {getLimitedText(test_spec[Constants._TS_]['test_description'], 0)} + {Constants.getLimitedText(test_spec[Constants._TS_]['test_description'], 0)} Expected Behavior: - {getLimitedText(test_spec[Constants._TS_]['expected_behavior'], 0)} + {Constants.getLimitedText(test_spec[Constants._TS_]['expected_behavior'], 0)} @@ -586,9 +572,9 @@ const MappingListingTable: React.FunctionComponent = ( - {getLimitedText(mappedItem[Constants._SR_]['title'], 0)} + {Constants.getLimitedText(mappedItem[Constants._SR_]['title'], 0)} - {getLimitedText(mappedItem[Constants._SR_]['description'], 0)} + {Constants.getLimitedText(mappedItem[Constants._SR_]['description'], 0)} @@ -663,7 +649,7 @@ const MappingListingTable: React.FunctionComponent = ( - {getLimitedText(mappedItem[Constants._J]['description'], 0)} + {Constants.getLimitedText(mappedItem[Constants._J]['description'], 0)} @@ -741,7 +727,7 @@ const MappingListingTable: React.FunctionComponent = ( - {getLimitedText(mappedItem[Constants._D]['description'], 0)} + {Constants.getLimitedText(mappedItem[Constants._D]['description'], 0)} @@ -801,7 +787,7 @@ const MappingListingTable: React.FunctionComponent = ( - Offset: {getLimitedText(mappedItem[Constants._D]['offset'], 0)} + Offset: {Constants.getLimitedText(mappedItem[Constants._D]['offset'], 0)} @@ -818,7 +804,7 @@ const MappingListingTable: React.FunctionComponent = ( - {getLimitedText(mappedItem[Constants._D]['section'], 0)} + {Constants.getLimitedText(mappedItem[Constants._D]['section'], 0)} @@ -901,7 +887,7 @@ const MappingListingTable: React.FunctionComponent = ( - {getLimitedText(work_item_description, 0)} + {Constants.getLimitedText(work_item_description, 0)} diff --git a/db/models/migration/sqlite_1_3_0.sql b/db/models/migration/sqlite_1_3_0.sql new file mode 100644 index 0000000..48bc47f --- /dev/null +++ b/db/models/migration/sqlite_1_3_0.sql @@ -0,0 +1,71 @@ +CREATE TABLE IF NOT EXISTS documents ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + title VARCHAR(200), + description VARCHAR, + document_type VARCHAR, + spdx_relation VARCHAR(200), + url VARCHAR, + section VARCHAR NOT NULL, + "offset" INTEGER NOT NULL, + valid INTEGER NOT NULL, + created_by_id INTEGER NOT NULL, + edited_by_id INTEGER NOT NULL, + status VARCHAR(30) NOT NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY(created_by_id) REFERENCES users (id), + FOREIGN KEY(edited_by_id) REFERENCES users (id) +); +CREATE TABLE IF NOT EXISTS documents_history ( + row_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + id INTEGER NOT NULL, + title VARCHAR(200), + description VARCHAR, + document_type VARCHAR, + spdx_relation VARCHAR(200), + url VARCHAR, + section VARCHAR NOT NULL, + "offset" INTEGER NOT NULL, + valid INTEGER NOT NULL, + created_by_id INTEGER NOT NULL, + edited_by_id INTEGER NOT NULL, + status VARCHAR(30) NOT NULL, + version INTEGER NOT NULL, + created_at DATETIME NOT NULL, + FOREIGN KEY(created_by_id) REFERENCES users (id), + FOREIGN KEY(edited_by_id) REFERENCES users (id) +); +CREATE TABLE IF NOT EXISTS document_mapping_api ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + api_id INTEGER NOT NULL, + document_id INTEGER NOT NULL, + section VARCHAR NOT NULL, + "offset" INTEGER NOT NULL, + coverage INTEGER NOT NULL, + created_by_id INTEGER NOT NULL, + edited_by_id INTEGER NOT NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY(api_id) REFERENCES apis (id), + FOREIGN KEY(document_id) REFERENCES documents (id), + FOREIGN KEY(created_by_id) REFERENCES users (id), + FOREIGN KEY(edited_by_id) REFERENCES users (id) +); +CREATE TABLE IF NOT EXISTS document_mapping_api_history ( + row_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + id INTEGER NOT NULL, + api_id INTEGER NOT NULL, + document_id INTEGER NOT NULL, + section VARCHAR NOT NULL, + "offset" INTEGER NOT NULL, + coverage INTEGER NOT NULL, + created_by_id INTEGER NOT NULL, + edited_by_id INTEGER NOT NULL, + version INTEGER NOT NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY(api_id) REFERENCES apis (id), + FOREIGN KEY(document_id) REFERENCES documents (id), + FOREIGN KEY(created_by_id) REFERENCES users (id), + FOREIGN KEY(edited_by_id) REFERENCES users (id) +); \ No newline at end of file diff --git a/docs/source/how_to_run_it.rst b/docs/source/how_to_run_it.rst index 407302e..d075824 100644 --- a/docs/source/how_to_run_it.rst +++ b/docs/source/how_to_run_it.rst @@ -25,6 +25,10 @@ Docker Containers You can deploy BASIL via Docker using Dockerfile-api and Dockerfile-app provided as part of the source code. +Or you can download images built in the [GitHub actions](https://github.com/elisa-tech/BASIL/actions). +Pay attention that those images are built with default value of build argument, as an example the ADMIN user password. +So it is suggested to use those images for evaluation or to modify them, changing critical informations before deploiying a BASIL instance. + # Build the Containers ^^^^^^^^^^^^^^^^^^^^^^ @@ -93,14 +97,14 @@ You can start the API Container above created with the name **basil-api-image** -d \ --privileged \ --network=host \ - --name basil-api-container \ + -p 0.0.0.0:: \ --mount source=basil-db-vol,target=/BASIL-API/db \ --mount source=basil-ssh-keys-vol,target=/BASIL-API/api/ssh_keys \ --mount source=basil-tmt-logs-vol,target=/var/tmp/tmt \ basil-api-image -the ``--privileged`` options is needed to be able to run fedora container as a possible testing environment inside the basil-api-container. +the ``--privileged`` options is needed to be able to run fedora container as a possible testing environment inside api container. You can start the APP Container above created with the name **basil-app-image** with the following docker command @@ -109,7 +113,7 @@ You can start the APP Container above created with the name **basil-app-image** docker run \ -d \ --network=host \ - --name basil-app-container \ + -p 0.0.0.0:: \ basil-app-image @@ -123,6 +127,20 @@ You can list running containers with the following docker command docker ps +# Inspect an Image +^^^^^^^^^^^^^^^^^^ + +You can inspect an image overriding the --entrypoint + +.. code-block:: bash + + docker run \ + --interactive \ + --tty \ + --network=host \ + --entrypoint=/bin/bash + + # Backup BASIL DB from Container ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -139,8 +157,8 @@ You can stop running containers, the one that you can see listed with the **ps** .. code-block:: bash - docker stop basil-api-container - docker stop basil-app-container + docker stop + docker stop diff --git a/docs/source/test_run.rst b/docs/source/test_run.rst index a498aaa..e0b328e 100644 --- a/docs/source/test_run.rst +++ b/docs/source/test_run.rst @@ -118,6 +118,27 @@ We will see in details both provisioning types, but let take a look to other fie + Environment variables (to allow you to enable different logic on the test side) + tmt context variables (to allow you to enable different logic in the tmt test) +Pay attention that BASIL is automatically exporting some environment variables that you can leverage in you tmt test: + + basil_test_case_id: ID of the Test Case + + basil_test_case_title: Title of the Test Case + + basil_api_api: Name of the Sw component (api) + + basil_api_library: Library of the Sw Component + + basil_api_library_version: Library Version of the Sw Component + + basil_test_case_mapping_table: Parent Type of the Test Case + + basil_test_case_mapping_id: Parent ID of the Test Case + + basil_test_relative_path: Relative Path of the Test Case inside the test repository + + basil_test_repo_path: Path of the Test Case repository + + basil_test_repo_url: Url of the Test Case repository + + basil_test_repo_ref: Ref (branch or commit sha) of the Test Case repository + + basil_test_run_id: UID of the Test Run + + basil_test_run_title: Title of the Test Run + + basil_test_run_config_id: ID of the Test Run Configuration + + basil_test_run_config_title: Title of the Test Run Configuration + + basil_user_email: Email of the User that created the Test Run + +Any other environment variable can be added by default in your BASIL instance customizing the api/tmttestrun.py file at TmtTestRunner() class initialization. + + # Test in Fedora Container ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/examples/tmt/ltp/syscall.fmf b/examples/tmt/ltp/syscall.fmf index d62b98e..ebdb05a 100644 --- a/examples/tmt/ltp/syscall.fmf +++ b/examples/tmt/ltp/syscall.fmf @@ -13,8 +13,9 @@ require: - make - pkgconf test: | - CURRENT_SYSCALL=${CURRENT_SYSCALL:=getpid} - CURRENT_TEST=${CURRENT_TEST:=getpid01} + CURRENT_SYSCALL=${basil_api_api:=getpid} + CURRENT_TEST=${basil_test_case_title:=getpid01} + CURRENT_TEST="${CURRENT_TEST%.*}" LTP_DOWNLOAD_PATH=/var/tmp/ltp LTP_BUILD_PATH=/opt/ltp GIT_SSL_NO_VERIFY=true git clone --depth 1 https://github.com/linux-test-project/ltp.git ${LTP_DOWNLOAD_PATH}