diff --git a/API/requirements.txt b/API/requirements.txt
new file mode 100644
index 000000000..abcf00bb8
--- /dev/null
+++ b/API/requirements.txt
@@ -0,0 +1,31 @@
+attrs==23.1.0
+blinker==1.6.3
+certifi==2023.7.22
+charset-normalizer==3.3.0
+click==8.1.7
+clickclick==20.10.2
+connexion==3.0.5
+Flask==2.2.2
+flask-marshmallow==0.14.0
+Flask-SQLAlchemy==3.0.3
+greenlet==3.0.0
+idna==3.4
+inflection==0.5.1
+itsdangerous==2.1.2
+Jinja2==3.1.2
+jsonschema==4.19.1
+jsonschema-specifications==2023.7.1
+MarkupSafe==2.1.3
+marshmallow==3.20.1
+marshmallow-sqlalchemy==0.29.0
+packaging==23.2
+PyYAML==6.0.1
+referencing==0.30.2
+requests==2.31.0
+rpds-py==0.10.3
+six==1.16.0
+SQLAlchemy==2.0.22
+swagger-ui-bundle==0.0.9
+typing_extensions==4.8.0
+urllib3==2.0.6
+Werkzeug==2.2.2
diff --git a/API/setup.cfg b/API/setup.cfg
new file mode 100644
index 000000000..f8f7f0e70
--- /dev/null
+++ b/API/setup.cfg
@@ -0,0 +1,14 @@
+[metadata]
+name = bkr.api
+
+[options]
+package_dir=
+ =src
+packages = find:
+zip_safe = False
+python_requires = >= 3
+[options.packages.find]
+where = src
+exclude =
+ tests*
+ .gitignore
diff --git a/API/setup.py b/API/setup.py
new file mode 100644
index 000000000..8bf1ba938
--- /dev/null
+++ b/API/setup.py
@@ -0,0 +1,2 @@
+from setuptools import setup
+setup()
diff --git a/API/src/bkr/api/__init__.py b/API/src/bkr/api/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/API/src/bkr/api/arches.py b/API/src/bkr/api/arches.py
new file mode 100644
index 000000000..9c4799acf
--- /dev/null
+++ b/API/src/bkr/api/arches.py
@@ -0,0 +1,23 @@
+from flask import abort
+
+ARCHES = {}
+
+def post(arch):
+ if arch and arch not in ARCHES:
+ ARCHES[arch] = {
+ "arch": arch,
+ }
+ return ARCHES[arch], 201
+ else:
+ abort(
+ 406,
+ f"{arch} already exists",
+ )
+
+def search(offset=0, limit=None):
+ start = 0
+ end = len(ARCHES.values())
+ if offset and limit:
+ start = offset * limit
+ end = start + limit
+ return list(ARCHES.values())[start:end]
diff --git a/API/src/bkr/api/health.py b/API/src/bkr/api/health.py
new file mode 100644
index 000000000..866d7fbb3
--- /dev/null
+++ b/API/src/bkr/api/health.py
@@ -0,0 +1,3 @@
+def search():
+ return {'msg': 'ok'}, 200
+
diff --git a/API/src/bkr/api/lab_controllers.py b/API/src/bkr/api/lab_controllers.py
new file mode 100644
index 000000000..f0cb14fbf
--- /dev/null
+++ b/API/src/bkr/api/lab_controllers.py
@@ -0,0 +1,34 @@
+from flask import abort
+
+LABCONTROLLERS = {}
+
+def post(lab_controller):
+ fqdn = lab_controller.get("fqdn")
+ user_name = lab_controller.get("user_name", "")
+ email_address = lab_controller.get("email_address", "")
+ password = lab_controller.get("password", "")
+
+ if fqdn and fqdn not in LABCONTROLLERS:
+ LABCONTROLLERS[fqdn] = {
+ "fqdn": fqdn,
+ "user_name": user_name,
+ "email_address": email_address,
+ "password": password,
+ }
+ return LABCONTROLLERS[fqdn], 201
+ else:
+ abort(
+ 406,
+ f"Lab Controller with fqdn {fqdn} already exists",
+ )
+
+def search(offset=0, limit=None):
+ start = 0
+ end = len(LABCONTROLLERS.values())
+ if offset and limit:
+ start = offset * limit
+ end = start + limit
+ return list(LABCONTROLLERS.values())[start:end]
+
+def get(lab_controller):
+ pass
diff --git a/API/src/bkr/api/systems/__init__.py b/API/src/bkr/api/systems/__init__.py
new file mode 100644
index 000000000..0bf1fd9d3
--- /dev/null
+++ b/API/src/bkr/api/systems/__init__.py
@@ -0,0 +1,78 @@
+from flask import abort
+from bkr.api.lab_controllers import LABCONTROLLERS
+from bkr.api.arches import ARCHES
+#from bkr.model import System
+
+SYSTEMS = {}
+
+def post(system):
+ fqdn = system.get("fqdn")
+ owner = system.get("owner")
+ status = system.get("status", "unavailable")
+ status_reason = system.get("status_reason", "")
+ arches = system.get("arches", [])
+ power = system.get("power", {})
+ location = system.get("location", "")
+ lender = system.get("lender", "")
+ vender = system.get("vender", "")
+ model = system.get("model", "")
+ serial = system.get("serial", "")
+ lab_controller = system.get("lab_controller", "")
+
+ if lab_controller and lab_controller not in LABCONTROLLERS:
+ abort(
+ 406,
+ f"Lab Controller {lab_controller} doesn't exist",
+ )
+
+ for arch in arches:
+ if arch not in ARCHES:
+ abort(
+ 406,
+ f"{arch} doesn't exist, create it first.",
+ )
+
+ if fqdn and fqdn not in SYSTEMS:
+ SYSTEMS[fqdn] = {
+ "fqdn": fqdn,
+ "owner": owner,
+ "status": status,
+ "status_reason": status_reason,
+ "arches": arches,
+ "power": power,
+ "location": location,
+ "lender": lender,
+ "vender": vender,
+ "model": model,
+ "serial": serial,
+ "lab_controller": lab_controller,
+ }
+ return SYSTEMS[fqdn], 201
+ else:
+ abort(
+ 406,
+ f"System with fqdn {fqdn} already exists",
+ )
+
+def search(offset=0, limit=None):
+ start = 0
+ end = len(SYSTEMS.values())
+ if offset and limit:
+ start = offset * limit
+ end = start + limit
+ return list(SYSTEMS.values())[start:end]
+
+def get(fqdn):
+ if fqdn in SYSTEMS:
+ return SYSTEMS[fqdn]
+ else:
+ abort(
+ 404,
+ f"System with fqdn {fqdn} not found",
+ )
+
+def put(fqdn):
+ pass
+
+def delete(fqdn):
+ pass
diff --git a/API/src/bkr/api/systems/access_policies.py b/API/src/bkr/api/systems/access_policies.py
new file mode 100644
index 000000000..59fcafba8
--- /dev/null
+++ b/API/src/bkr/api/systems/access_policies.py
@@ -0,0 +1,11 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/activity.py b/API/src/bkr/api/systems/activity.py
new file mode 100644
index 000000000..59fcafba8
--- /dev/null
+++ b/API/src/bkr/api/systems/activity.py
@@ -0,0 +1,11 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/cc.py b/API/src/bkr/api/systems/cc.py
new file mode 100644
index 000000000..90441970d
--- /dev/null
+++ b/API/src/bkr/api/systems/cc.py
@@ -0,0 +1,8 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/commands.py b/API/src/bkr/api/systems/commands.py
new file mode 100644
index 000000000..59fcafba8
--- /dev/null
+++ b/API/src/bkr/api/systems/commands.py
@@ -0,0 +1,11 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/excluded_families.py b/API/src/bkr/api/systems/excluded_families.py
new file mode 100644
index 000000000..59fcafba8
--- /dev/null
+++ b/API/src/bkr/api/systems/excluded_families.py
@@ -0,0 +1,11 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/executed_tasks.py b/API/src/bkr/api/systems/executed_tasks.py
new file mode 100644
index 000000000..59fcafba8
--- /dev/null
+++ b/API/src/bkr/api/systems/executed_tasks.py
@@ -0,0 +1,11 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/install_options.py b/API/src/bkr/api/systems/install_options.py
new file mode 100644
index 000000000..59fcafba8
--- /dev/null
+++ b/API/src/bkr/api/systems/install_options.py
@@ -0,0 +1,11 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/loan.py b/API/src/bkr/api/systems/loan.py
new file mode 100644
index 000000000..d0f51589d
--- /dev/null
+++ b/API/src/bkr/api/systems/loan.py
@@ -0,0 +1,14 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def put():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/notes.py b/API/src/bkr/api/systems/notes.py
new file mode 100644
index 000000000..59fcafba8
--- /dev/null
+++ b/API/src/bkr/api/systems/notes.py
@@ -0,0 +1,11 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/problem_reports.py b/API/src/bkr/api/systems/problem_reports.py
new file mode 100644
index 000000000..59fcafba8
--- /dev/null
+++ b/API/src/bkr/api/systems/problem_reports.py
@@ -0,0 +1,11 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/reservation.py b/API/src/bkr/api/systems/reservation.py
new file mode 100644
index 000000000..d0f51589d
--- /dev/null
+++ b/API/src/bkr/api/systems/reservation.py
@@ -0,0 +1,14 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def put():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/status.py b/API/src/bkr/api/systems/status.py
new file mode 100644
index 000000000..59fcafba8
--- /dev/null
+++ b/API/src/bkr/api/systems/status.py
@@ -0,0 +1,11 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/systems/system_keys.py b/API/src/bkr/api/systems/system_keys.py
new file mode 100644
index 000000000..59fcafba8
--- /dev/null
+++ b/API/src/bkr/api/systems/system_keys.py
@@ -0,0 +1,11 @@
+def search():
+ pass
+
+def post():
+ pass
+
+def get():
+ pass
+
+def delete():
+ pass
diff --git a/API/src/bkr/api/templates/home.html b/API/src/bkr/api/templates/home.html
new file mode 100644
index 000000000..3b9572830
--- /dev/null
+++ b/API/src/bkr/api/templates/home.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Flask REST API
+
+
+
+ Hello, World!
+
+
+
diff --git a/API/src/bkr/app.py b/API/src/bkr/app.py
new file mode 100644
index 000000000..e980f40bf
--- /dev/null
+++ b/API/src/bkr/app.py
@@ -0,0 +1,18 @@
+import logging
+import sys
+import time
+from connexion.resolver import RestyResolver
+from bkr import config
+
+logger = logging.getLogger(__name__)
+
+
+def create_app():
+ app = config.connex_app
+ app.add_api("swagger.yml", resolver=RestyResolver('bkr.api'))
+ return app
+
+
+if __name__ == "__main__":
+ app = create_app()
+ app.run(host="0.0.0.0", port=8000)
diff --git a/API/src/bkr/config.py b/API/src/bkr/config.py
new file mode 100644
index 000000000..27d12266b
--- /dev/null
+++ b/API/src/bkr/config.py
@@ -0,0 +1,29 @@
+import os
+import pathlib
+
+import sqlalchemy
+import connexion
+from flask_sqlalchemy import SQLAlchemy
+from flask_marshmallow import Marshmallow
+
+
+basedir = pathlib.Path(__file__).parent.resolve()
+connex_app = connexion.App(__name__, specification_dir=basedir)
+
+app = connex_app.app
+app.config.from_object("bkr.settings")
+app.config.from_object(os.environ.get("BKR_SETTINGS_MODULE"))
+
+config = app.config
+db = SQLAlchemy(app)
+ma = Marshmallow(app)
+
+
+def get_engine(db_uri):
+ return sqlalchemy.create_engine(
+ db_uri,
+ pool_size=app.config["SQLALCHEMY_POOL_SIZE"],
+ max_overflow=app.config["SQLALCHEMY_MAX_OVERFLOW"],
+ encoding="utf8",
+ )
+
diff --git a/API/src/bkr/settings.py b/API/src/bkr/settings.py
new file mode 100644
index 000000000..8b6827e21
--- /dev/null
+++ b/API/src/bkr/settings.py
@@ -0,0 +1,41 @@
+# Global parameters about the API itself
+#
+import os
+
+HOST = os.getenv("API_HOST", "127.0.0.1")
+PORT = int(os.getenv("API_PORT", "5000"))
+DEBUG = True
+JSONIFY_PRETTYPRINT_REGULAR = False
+
+# Database (SQLAlchemy) related parameters
+#
+DB_USER = os.getenv("DB_USER", "bkr")
+DB_PASSWORD = os.getenv("DB_PASSWORD", "bkr")
+DB_HOST = os.getenv("DB_HOST", "127.0.0.1")
+DB_PORT = int(os.getenv("DB_PORT", "5432"))
+DB_NAME = os.getenv("DB_NAME", "beaker")
+DEFAULT_SQLALCHEMY_DATABASE_URI = (
+ "postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}".format(
+ db_user=DB_USER,
+ db_password=DB_PASSWORD,
+ db_host=DB_HOST,
+ db_port=DB_PORT,
+ db_name=DB_NAME,
+ )
+)
+SQLALCHEMY_DATABASE_URI = os.getenv(
+ "SQLALCHEMY_DATABASE_URI", DEFAULT_SQLALCHEMY_DATABASE_URI
+)
+
+# The following two lines will output the SQL statements
+# executed by SQLAlchemy. Useful while debugging and in
+# development. Turned off by default
+# --------
+SQLALCHEMY_ECHO = False
+SQLALCHEMY_NATIVE_UNICODE = True
+SQLALCHEMY_POOL_SIZE = 5
+SQLALCHEMY_MAX_OVERFLOW = 25
+
+# Logging related parameters
+LOG_LEVEL = "INFO"
+LOG_FORMAT = "[%(asctime)s] %(levelname)-8s %(name)-12s %(message)s"
diff --git a/API/src/bkr/swagger.yml b/API/src/bkr/swagger.yml
new file mode 100644
index 000000000..7b3e0fa03
--- /dev/null
+++ b/API/src/bkr/swagger.yml
@@ -0,0 +1,768 @@
+openapi: 3.0.0
+servers:
+ - description: Beaker API
+ url: /api
+info:
+ version: "1.0.0"
+ title: Sample Application Project
+ description: >-
+ Sample Beaker API.
+paths:
+ /health:
+ get:
+ tags: [Health]
+ description: Health Check
+ responses:
+ '200':
+ description: Status message from server describing current health
+ /systems:
+ get:
+ tags:
+ - Systems
+ description: >-
+ All the systems registered in Inventory
+ parameters:
+ - $ref: '#/components/parameters/PageLimit'
+ - $ref: '#/components/parameters/PageOffset'
+ responses:
+ "200":
+ description: "Successfully read systems list"
+ content:
+ application/json:
+ schema:
+ type: "array"
+ items:
+ $ref: "#/components/schemas/System"
+ application/xml:
+ schema:
+ type: "array"
+ xml:
+ name: systems
+ items:
+ $ref: "#/components/schemas/System"
+ post:
+ tags:
+ - Systems
+ description: "Create a System"
+ requestBody:
+ x-body-name: "system"
+ description: "System to create"
+ required: True
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/System"
+ examples:
+ SystemExample:
+ $ref: '#/components/examples/System'
+ responses:
+ "201":
+ description: "Successfully created System"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system"
+ '400':
+ $ref: '#/components/responses/400Error'
+ put:
+ tags:
+ - Systems
+ description: "Update a System"
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully updated System"
+ '201':
+ description: "Successfully created System"
+ '204':
+ description: "No changes for System"
+ '400':
+ $ref: '#/components/responses/400Error'
+ requestBody:
+ x-body-name: "system"
+ description: "System to update"
+ required: True
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/System"
+ examples:
+ SystemExample:
+ $ref: '#/components/examples/System'
+ delete:
+ tags:
+ - Systems
+ description: "Delete a System"
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ "204":
+ description: "Successfully deleted System"
+ /systems/{fqdn}/cc:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system CC entries
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system CC entries"
+ '400':
+ $ref: '#/components/responses/400Error'
+ post:
+ tags:
+ - Systems
+ description: "Add cc email to system"
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ requestBody:
+ x-body-name: "email"
+ description: "System to create"
+ required: True
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EMAIL"
+ examples:
+ SystemExample:
+ $ref: '#/components/examples/EMAIL'
+ responses:
+ "201":
+ description: "Successfully created CC entry for System"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/cc/{email}:
+ delete:
+ tags:
+ - Systems
+ description: Delete a system CC entry
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ - $ref: '#/components/parameters/EMAIL'
+ responses:
+ '204':
+ description: "Successfully deleted system CC entry"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/problem-reports:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system problem reports
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system problem reports"
+ '400':
+ $ref: '#/components/responses/400Error'
+ post:
+ tags:
+ - Systems
+ description: Add problem report to system
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ requestBody:
+ x-body-name: "problem_report"
+ description: "Problem report to create"
+ required: True
+ content:
+ application/json:
+ schema:
+ type: "object"
+ required:
+ - message
+ properties:
+ message:
+ type: "string"
+ examples:
+ reportProblemExample:
+ value:
+ message: >-
+ This system is not powering on and is failing to netboot.
+ responses:
+ "201":
+ description: "Successfully created problem report entry for System"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/problem-reports/{id}:
+ delete:
+ tags:
+ - Systems
+ description: Delete a system problem report entry
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ - $ref: '#/components/parameters/ID'
+ responses:
+ '204':
+ description: "Successfully deleted system problem report entry"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/problem-reports/{id}:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system problem report
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ - $ref: '#/components/parameters/ID'
+ responses:
+ '200':
+ description: "Successfully returned a system problem report"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/reservation:
+ put:
+ tags:
+ - Systems
+ description: "Reserve system"
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ requestBody:
+ x-body-name: "reserve"
+ description: "Reserve a system"
+ required: True
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EMAIL"
+ examples:
+ SystemExample:
+ $ref: '#/components/examples/EMAIL'
+ responses:
+ "201":
+ description: "Successfully Reserved System"
+ '400':
+ $ref: '#/components/responses/400Error'
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system reservation
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system reservation"
+ '400':
+ $ref: '#/components/responses/400Error'
+ delete:
+ tags:
+ - Systems
+ description: Delete a system reservation
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '204':
+ description: "Successfully deleted system reservation"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/loan:
+ put:
+ tags:
+ - Systems
+ description: "A loan gives exclusive access to a system"
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ requestBody:
+ x-body-name: "loan"
+ description: "Loan a system"
+ required: True
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/EMAIL"
+ examples:
+ SystemExample:
+ $ref: '#/components/examples/EMAIL'
+ responses:
+ "201":
+ description: "Successfully Reserved System"
+ '400':
+ $ref: '#/components/responses/400Error'
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system loan
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system loan"
+ '400':
+ $ref: '#/components/responses/400Error'
+ delete:
+ tags:
+ - Systems
+ description: Delete a system loan
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '204':
+ description: "Successfully deleted system loan"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/access_policies:
+ post:
+ tags:
+ - Systems
+ description: Add an access policy to a system
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ requestBody:
+ x-body-name: "access_policy"
+ description: "Access policy to create"
+ required: True
+ content:
+ application/json:
+ schema:
+ type: "object"
+ required:
+ - message
+ properties:
+ message:
+ type: "string"
+ examples:
+ reportProblemExample:
+ value:
+ message: >-
+ This system is not powering on and is failing to netboot.
+ responses:
+ "201":
+ description: "Successfully created access policy entry for System"
+ '400':
+ $ref: '#/components/responses/400Error'
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system access policies
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system access policies"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/access_policies/{id}:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system access policy
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ - $ref: '#/components/parameters/ID'
+ responses:
+ '200':
+ description: "Successfully returned a system access policy"
+ '400':
+ $ref: '#/components/responses/400Error'
+ delete:
+ tags:
+ - Systems
+ description: Delete a system access policy
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ - $ref: '#/components/parameters/ID'
+ responses:
+ '204':
+ description: "Successfully deleted system access policy"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/status:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system status
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system status"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/commands:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system commands
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system commands"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/commands/{id}:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system command
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ - $ref: '#/components/parameters/ID'
+ responses:
+ '200':
+ description: "Successfully returned a system command"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/notes:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system notes
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system notes"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/notes/{id}:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system note
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ - $ref: '#/components/parameters/ID'
+ responses:
+ '200':
+ description: "Successfully returned a system note"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/activity:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system activity
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system activity"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/executed-tasks:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system executed tasks
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system executed tasks"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/install-options:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system install options
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system install options"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/install-options/{id}:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system install option
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ - $ref: '#/components/parameters/ID'
+ responses:
+ '200':
+ description: "Successfully returned a system install option"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/excluded-families:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system excluded families
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system excluded families"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/excluded-families/{id}:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system excluded family
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ - $ref: '#/components/parameters/ID'
+ responses:
+ '200':
+ description: "Successfully returned a system excluded family"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/system-keys:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system keys
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a system keys"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /systems/{fqdn}/system-keys/{id}:
+ get:
+ tags:
+ - Systems
+ description: Obtain information about a system key
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ - $ref: '#/components/parameters/ID'
+ responses:
+ '200':
+ description: "Successfully returned a system key"
+ /arches:
+ get:
+ tags:
+ - Arches
+ description: >-
+ All the arches registered in Inventory
+ responses:
+ '200':
+ description: "Successfully read arch list"
+ /lab_controllers:
+ get:
+ tags:
+ - Lab-Controllers
+ description: >-
+ This operation shows how to override the global security defined above,
+ as we want to open it up for all users.
+ security: []
+ parameters:
+ - $ref: '#/components/parameters/PageLimit'
+ - $ref: '#/components/parameters/PageOffset'
+ responses:
+ '200':
+ description: "Successfully read lab controller list"
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ fqdn:
+ type: string
+ post:
+ tags:
+ - Lab-Controllers
+ description: "Create a Lab Controller"
+ requestBody:
+ x-body-name: "lab_controller"
+ description: "Lab Controller to create"
+ required: True
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/LabController"
+ examples:
+ SystemExample:
+ $ref: '#/components/examples/LabController'
+ responses:
+ "201":
+ description: "Successfully created Lab Controller"
+ '400':
+ $ref: '#/components/responses/400Error'
+ /lab_controllers/{fqdn}:
+ get:
+ tags:
+ - Lab-Controllers
+ description: Obtain information about a lab controller
+ parameters:
+ - $ref: '#/components/parameters/FQDN'
+ responses:
+ '200':
+ description: "Successfully returned a lab controller"
+ '400':
+ $ref: '#/components/responses/400Error'
+components:
+ parameters:
+ PageLimit:
+ name: limit
+ in: query
+ description: Limits the number of items on a page
+ schema:
+ type: integer
+ examples:
+ limit-example:
+ value: 100
+ PageOffset:
+ name: offset
+ in: query
+ description: Specifies the page number of the items to be displayed
+ schema:
+ type: integer
+ examples:
+ offset-example:
+ value: 0
+ FQDN:
+ name: "fqdn"
+ description: "Fully qualified domain name of system to get"
+ in: path
+ required: True
+ schema:
+ type: "string"
+ examples:
+ fqdn-example:
+ value: "host.example.com"
+ ID:
+ name: "id"
+ description: "id of the record to get"
+ in: path
+ required: True
+ schema:
+ type: "string"
+ examples:
+ id-example:
+ value: "1262"
+ EMAIL:
+ name: "email"
+ description: "Email address to include in Carbon Copy (CC)"
+ in: path
+ required: True
+ schema:
+ type: "string"
+ examples:
+ email-example:
+ value: "user@example.com"
+ schemas:
+ EMAIL:
+ type: "object"
+ required:
+ - email
+ properties:
+ email:
+ type: "string"
+ LabController:
+ type: "object"
+ required:
+ - fqdn
+ properties:
+ fqdn:
+ type: "string"
+ user_name:
+ type: "string"
+ email_address:
+ type: "string"
+ password:
+ type: "string"
+ removed:
+ type: "boolean"
+ disabled:
+ type: "boolean"
+ System:
+ type: "object"
+ required:
+ - fqdn
+ properties:
+ fqdn:
+ type: "string"
+ owner:
+ type: "string"
+ status:
+ type: "string"
+ status_reason:
+ type: "string"
+ arches:
+ type: "array"
+ items:
+ type: "string"
+ power:
+ type: "object"
+ properties:
+ power_type:
+ type: "string"
+ power_address:
+ type: "string"
+ power_user:
+ type: "string"
+ power_password:
+ type: "string"
+ power_id:
+ type: "string"
+ power_quiescent_period:
+ type: "integer"
+ release_action:
+ type: "string"
+ reprovision_distro_tree:
+ type: "string"
+ location:
+ type: "string"
+ lender:
+ type: "string"
+ vender:
+ type: "string"
+ model:
+ type: "string"
+ serial:
+ type: "string"
+ lab_controller:
+ type: "string"
+ responses:
+ 400Error:
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ examples:
+ EMAIL:
+ value:
+ email: "user@example.com"
+ LabController:
+ value:
+ fqdn: "lab1.example.com"
+ user_name: "host/labctrl"
+ email_address: "labctrl@beaker-server.localdomain"
+ password: "labctrl"
+ removed: "false"
+ disabled: "false"
+ System:
+ value:
+ fqdn: "host.example.com"
+ owner: "user@fedora.com"
+ status: "available"
+ status_reason: ""
+ arches:
+ - "x86_64"
+ power:
+ power_type: "ipmi"
+ power_address: "127.0.0.1"
+ power_user: "admin"
+ power_password: "admin"
+ power_id: "6231"
+ power_quiescent_period: 60
+ release_action: "PowerOff"
+ reprovision_distro_tree: ""
+ location: "Westford, MA"
+ lender: "IBM"
+ vender: "IBM"
+ model: "Z230"
+ serial: "A12345678"
+ lab_controller: "lab1.example.com"
diff --git a/API/tests/conftest.py b/API/tests/conftest.py
new file mode 100644
index 000000000..2eb2969f6
--- /dev/null
+++ b/API/tests/conftest.py
@@ -0,0 +1,14 @@
+import pytest
+from bkr.app import create_app
+
+app = create_app()
+
+@pytest.fixture()
+def client():
+ with app.test_client() as c:
+ yield c
+
+
+@pytest.fixture()
+def runner():
+ return app.test_cli_runner()
diff --git a/API/tests/settings.py b/API/tests/settings.py
new file mode 100644
index 000000000..848c0d52b
--- /dev/null
+++ b/API/tests/settings.py
@@ -0,0 +1,3 @@
+DEBUG = False
+
+LOG_FILE = "/dev/null"
diff --git a/API/tests/test_api.py b/API/tests/test_api.py
new file mode 100644
index 000000000..aeed604e3
--- /dev/null
+++ b/API/tests/test_api.py
@@ -0,0 +1,4 @@
+def test_health(client):
+ response = client.get('/api/health')
+ assert response.status_code == 200
+
diff --git a/API/tests/test_systems.py b/API/tests/test_systems.py
new file mode 100644
index 000000000..fd9dea157
--- /dev/null
+++ b/API/tests/test_systems.py
@@ -0,0 +1,30 @@
+def test_lab_controller_create(client):
+ response = client.post("/api/lab_controllers", json={
+ "fqdn": "lab1.example.com"
+ })
+ assert response.json()["fqdn"] == "lab1.example.com"
+
+def test_system_create(client):
+ response = client.post("/api/systems", json={
+ "fqdn": "host1.example.com",
+ "location": "Westford, MA",
+ "lender": "IBM",
+ "vender": "IBM",
+ "model": "Z230",
+ "serial": "A12345678",
+ "lab_controller": "lab1.example.com"
+ })
+ assert response.json()["fqdn"] == "host1.example.com"
+ assert response.json()["status"] == "unavailable"
+
+def test_system_fail(client):
+ response = client.post("/api/systems", json={
+ "fqdn": "host4.example.com",
+ "location": "Westford, MA",
+ "lender": "IBM",
+ "vender": "IBM",
+ "model": "Z230",
+ "serial": "A12345678",
+ "lab_controller": "nolab.example.com"
+ })
+ assert response.status_code == 406