v1 server and lifecycle manager working. execution contract added
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
.git
|
||||
.gitignore
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.venv/
|
||||
venv/
|
||||
@@ -0,0 +1,9 @@
|
||||
FROM python:3.13-slim
|
||||
|
||||
WORKDIR /rmmanager
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
from plugins import rmass
|
||||
from core.operation.operation import buildOperation
|
||||
|
||||
outputs = buildOperation(rmass.checkavailableoperation)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
from .apirequest import executeRequest
|
||||
from .runtime import buildSave, loadSave, buildLog, manageFailure, buildOutput
|
||||
#from .execution import buildOperation, buildAction
|
||||
from .execution.action import buildAction
|
||||
from .execution.operation import buildOperation
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
from .execute import executeRequest
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
import requests
|
||||
|
||||
from .request import buildRequest
|
||||
|
||||
session = requests.Session()
|
||||
|
||||
def executeRequest(definition, outputs):
|
||||
request = buildRequest(definition, outputs)
|
||||
|
||||
response = session.request(
|
||||
method=request["method"],
|
||||
url=request["path"],
|
||||
json=request.get("object"),
|
||||
headers=request.get("headers")
|
||||
)
|
||||
|
||||
# if definition hasattr(cookies):
|
||||
# sessioncookies = {}
|
||||
|
||||
# for cookie_name, cookie_key in cookies:
|
||||
|
||||
# session.cookies.set(
|
||||
# sessioncookies[cookie_name] = response[cookie_key],
|
||||
|
||||
|
||||
return response
|
||||
@@ -0,0 +1,9 @@
|
||||
def buildKey(key, outputs):
|
||||
value = key["keyvalue"]
|
||||
|
||||
if isinstance(value, dict) and "output" in value:
|
||||
value = outputs[value["output"]]
|
||||
|
||||
return {
|
||||
key["keytitle"]: value,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
from .key import buildKey
|
||||
|
||||
def buildObject(keys, outputs):
|
||||
object = {}
|
||||
|
||||
for key in keys:
|
||||
object.update(
|
||||
buildKey(key, outputs),
|
||||
)
|
||||
|
||||
return object
|
||||
@@ -0,0 +1,28 @@
|
||||
from .object import buildObject
|
||||
|
||||
def buildRequest(definition, outputs):
|
||||
request = {
|
||||
"method": definition.method,
|
||||
"path": definition.path,
|
||||
}
|
||||
|
||||
if hasattr(definition, "keys"):
|
||||
request["object"] = buildObject(definition.keys, outputs)
|
||||
|
||||
if hasattr(definition, "headers"):
|
||||
headers = {}
|
||||
|
||||
for header_name, header_value in definition.headers.items():
|
||||
value = header_value
|
||||
|
||||
if isinstance(value, dict) and "output" in header_value:
|
||||
value = outputs[value["output"]]
|
||||
|
||||
if isinstance(header_value, dict) and "prefix" in header_value:
|
||||
value = header_value["prefix"] + value
|
||||
|
||||
headers[header_name] = value
|
||||
|
||||
request["headers"] = headers
|
||||
|
||||
return request
|
||||
@@ -0,0 +1,2 @@
|
||||
#from . import action
|
||||
#from . import operation
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
from .action import buildAction
|
||||
#from .runtime import buildSave, loadSave, buildLog, manageFailure, buildOutput
|
||||
#rom .orchestrator import buildOperation
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,25 @@
|
||||
from core.apirequest import executeRequest
|
||||
from core.runtime.savedata import loadSave, buildSave
|
||||
from core.runtime.outputs import buildOutput
|
||||
from core.runtime.logs import buildLog
|
||||
|
||||
def buildAction (action, outputs):
|
||||
|
||||
if hasattr(action, "loaddata"):
|
||||
outputs = loadSave(outputs, action)
|
||||
|
||||
response = executeRequest(action, outputs)
|
||||
|
||||
if hasattr(action, "outputs"):
|
||||
outputs = buildOutput(response, action, outputs)
|
||||
|
||||
if hasattr(action, "savedata"):
|
||||
save_file = buildSave(outputs, action)
|
||||
|
||||
if hasattr(action, "prints"):
|
||||
logs = buildLog(action, outputs)
|
||||
|
||||
if action.showstatus is True and response.status_code == 200:
|
||||
print(f"{action.name} successful")
|
||||
|
||||
return outputs, response
|
||||
@@ -0,0 +1,9 @@
|
||||
from core.execution.action import buildAction
|
||||
from core.execution.operation import buildOperation
|
||||
|
||||
def executeContract(module)
|
||||
if module.kind == "operation":
|
||||
outputs = buildOperation(module)
|
||||
|
||||
elif module.kind == "action":
|
||||
outputs = buildAction(module)
|
||||
@@ -0,0 +1 @@
|
||||
from .operation import buildOperation
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,41 @@
|
||||
import logging
|
||||
#from core import buildAction
|
||||
from core.runtime.savedata import buildSave, loadSave
|
||||
from core.runtime.logs import buildLog
|
||||
from core.runtime.outputs import buildOutput
|
||||
from core.runtime.managefailure import manageFailure
|
||||
from core.execution.action.action import buildAction
|
||||
from .skipstep import skipStep
|
||||
|
||||
def buildOperation(definition):
|
||||
actions = definition.actions
|
||||
|
||||
# responses = []
|
||||
outputs = {}
|
||||
|
||||
for action in actions:
|
||||
try:
|
||||
if hasattr(definition, "skipstep"):
|
||||
if skipStep(definition, action, outputs):
|
||||
continue
|
||||
|
||||
outputs, response = buildAction(action, outputs)
|
||||
# responses.append(response)
|
||||
|
||||
except Exception as error:
|
||||
outputs = manageFailure(error, action, outputs)
|
||||
|
||||
# if hasattr(action, "cookies"):
|
||||
# for cookiename, cookie, in cookies:
|
||||
|
||||
# session.cookies.set(
|
||||
# cookiename,
|
||||
# outputs[cookie],
|
||||
# domain=domainname
|
||||
# )
|
||||
|
||||
# print(
|
||||
# {outputs[cookie]},
|
||||
# domain,
|
||||
# cookiename,
|
||||
# )
|
||||
@@ -0,0 +1,14 @@
|
||||
def skipStep(definition, action, outputs):
|
||||
try:
|
||||
if action in definition.skipstep:
|
||||
condition = definition.skipstep[action]
|
||||
|
||||
current_value = outputs[condition["output"]]
|
||||
|
||||
if condition["operator"] == ">=":
|
||||
if current_value >= condition["value"]:
|
||||
print(f"Skipping {action.name}")
|
||||
return True
|
||||
|
||||
except TypeError, KeyError:
|
||||
return False
|
||||
@@ -0,0 +1 @@
|
||||
from . import manager
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,25 @@
|
||||
from datetime import datetime
|
||||
from core.operation.operation import buildOperation
|
||||
|
||||
def buildManager(definition):
|
||||
operations = definition.operations
|
||||
|
||||
current_time = datetime.now().time()
|
||||
|
||||
for operation in operations:
|
||||
if hasattr(operation, "role") and operation.role == "activate":
|
||||
if definition.uptime <= current_time <= definition.downtime:
|
||||
while True:
|
||||
current_time = datetime.now().time()
|
||||
outputs = buildOperation(operation)
|
||||
|
||||
if hasattr(operation, "role") and operation.role == "deactivate":
|
||||
if definition.downtime <= current_time <= definition.uptime:
|
||||
while True:
|
||||
current_time = datetime.now().time()
|
||||
outputs = buildOperation(operation)
|
||||
|
||||
# if hasattr(operation, "role") and role == offline:
|
||||
# outputs = buildOperation(operation)
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#from .managefailure import manageFailure
|
||||
#from . import outputs
|
||||
#from . import managefailure
|
||||
#from . import savedata
|
||||
from .outputs import buildOutput
|
||||
from .savedata import buildSave, loadSave
|
||||
from .logs import buildLog
|
||||
#from . import managefailure
|
||||
from .managefailure import manageFailure
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
def buildLog (action, outputs):
|
||||
# try:
|
||||
for print_name, print_key in action.prints.items():
|
||||
print(
|
||||
f"{print_name}:",
|
||||
f"{outputs[print_key]}",
|
||||
)
|
||||
|
||||
# except KeyError:
|
||||
#outputs[print_key] = "N/A"
|
||||
# return
|
||||
@@ -0,0 +1,19 @@
|
||||
from core.runtime.outputs import buildOutput
|
||||
from core.execution.action.action import buildAction
|
||||
#from core.runtime.outputs import buildOutput
|
||||
|
||||
def manageFailure(error, action, outputs):
|
||||
if hasattr(action, "recoverPolicy"):
|
||||
errors = action.recoverPolicy
|
||||
error_type = type(error)
|
||||
|
||||
print(f"{type(error).__name__} detected during {action.name}. Applying recovery policy.")
|
||||
|
||||
if error_type in errors:
|
||||
repairs = errors[error_type]
|
||||
|
||||
for repair in repairs:
|
||||
outputs, response = buildAction(repair, outputs)
|
||||
|
||||
return outputs
|
||||
print(f"{error}")
|
||||
@@ -0,0 +1,21 @@
|
||||
def buildOutput(response, action, outputs):
|
||||
|
||||
response_data = response.json()
|
||||
response_objects = [response_data]
|
||||
|
||||
while response_objects:
|
||||
response_object = response_objects.pop()
|
||||
|
||||
for key, value, in response_object.items():
|
||||
|
||||
if isinstance(value, dict):
|
||||
response_objects.append(value)
|
||||
|
||||
for output_name, response_key in action.outputs.items():
|
||||
if key == response_key:
|
||||
outputs[output_name] = value
|
||||
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import json
|
||||
|
||||
def buildSave(outputs, action):
|
||||
save_data = {}
|
||||
|
||||
for save_name in action.savedata:
|
||||
save_data[save_name] = outputs[save_name]
|
||||
|
||||
with open(f"{action.savepath}", "w") as save_file:
|
||||
json.dump(save_data, save_file)
|
||||
|
||||
print(f"{save_name} saved by {action.name}")
|
||||
|
||||
return save_file
|
||||
|
||||
def loadSave(outputs, action):
|
||||
with open(f"{action.savepath}", "r") as save_file:
|
||||
load_data = json.load(save_file)
|
||||
|
||||
for load_name in action.loaddata:
|
||||
outputs[load_name] = load_data[load_name]
|
||||
print(f"{load_name} loaded by {action.name}")
|
||||
|
||||
return outputs
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
from core.execution.action import buildAction
|
||||
from core.execution.operation import buildOperation
|
||||
import plugins
|
||||
import importlib
|
||||
import threading
|
||||
from lifecyclemanager import manageLifecycle
|
||||
|
||||
lifecycles = {}
|
||||
|
||||
def buildDispatch(request):
|
||||
dispatch = []
|
||||
|
||||
for key in request["module"]:
|
||||
dispatch.append(request["module"][key])
|
||||
|
||||
resolveddispatch = "plugins."+ ".".join(dispatch)
|
||||
|
||||
module = importlib.import_module(resolveddispatch)
|
||||
|
||||
mode = request.get("mode", "oneshot")
|
||||
|
||||
if mode == "continuous":
|
||||
|
||||
state = {
|
||||
"mode": "continuous"
|
||||
}
|
||||
|
||||
lifecycles[resolveddispatch] = state
|
||||
|
||||
print(f"Dispatching: {module.name}")
|
||||
|
||||
thread = threading.Thread(
|
||||
target=manageLifecycle,
|
||||
args=(module, state)
|
||||
)
|
||||
|
||||
thread.start()
|
||||
print(f"Started lifecycle: {module.name}")
|
||||
|
||||
elif mode == "stop":
|
||||
if resolveddispatch in lifecycles:
|
||||
state = lifecycles[resolveddispatch]
|
||||
state["mode"] = "stop"
|
||||
print(f"Stopping lifecycle: {module.name}")
|
||||
else:
|
||||
print(f"No registered lifecycle: {module.name}")
|
||||
|
||||
elif mode == "oneshot":
|
||||
outputs = manageLifecycle(module, {"mode": "oneshot"})
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported lifecycle mode {mode}")
|
||||
|
||||
return
|
||||
#outputs = manageLifecycle(module, state)
|
||||
@@ -0,0 +1,23 @@
|
||||
from core.execution.operation import buildOperation
|
||||
from core.execution.action import buildAction
|
||||
import time
|
||||
|
||||
def executeContract(module):
|
||||
if module.kind == "operation":
|
||||
outputs = buildOperation(module)
|
||||
|
||||
elif module.kind == "action":
|
||||
outputs = buildAction(module)
|
||||
|
||||
|
||||
def manageLifecycle(module, state):
|
||||
if state["mode"] == "continuous":
|
||||
while state["mode"] == "continuous":
|
||||
outputs = executeContract(module)
|
||||
|
||||
time.sleep(module.sleeptime)
|
||||
|
||||
elif state["mode"] == "oneshot":
|
||||
outputs = executeContract(module)
|
||||
|
||||
return
|
||||
@@ -0,0 +1,5 @@
|
||||
from plugins import rmass
|
||||
from core.execution.operation import buildOperation
|
||||
|
||||
outputs = buildOperation(rmass.operations.login)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from plugins import rmass
|
||||
from operation import buildOperation
|
||||
|
||||
outputs = buildOperation(rmass.checkavailableoperation)
|
||||
@@ -0,0 +1,4 @@
|
||||
from core.manager.manager import buildManager
|
||||
from plugins import rmass
|
||||
|
||||
outputs = buildManager(rmass.manager)
|
||||
@@ -0,0 +1 @@
|
||||
from . import rmass
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
from . import actions, operations
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
from . import login, getcsrf, refresh_tokens, checkavailable, setavailable, setunavailable, setnotset
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,30 @@
|
||||
name = "Availability check"
|
||||
|
||||
kind = "action"
|
||||
|
||||
showstatus = False
|
||||
|
||||
method = "GET"
|
||||
|
||||
path = "https://rentmasseur.com/api/v1/account/keeponline"
|
||||
|
||||
outputs = {
|
||||
"message": "message",
|
||||
"validTo": "validTo",
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": {
|
||||
"output": "access_token",
|
||||
"prefix": "Bearer ",
|
||||
},
|
||||
}
|
||||
|
||||
prints = {
|
||||
"Status": "message",
|
||||
"Time Remaining": "validTo",
|
||||
}
|
||||
|
||||
recoverPolicy = {
|
||||
KeyError: []
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
name = "Get CSRF token"
|
||||
|
||||
kind = "action"
|
||||
|
||||
showstatus = True
|
||||
|
||||
method = "GET"
|
||||
|
||||
path = "https://rentmasseur.com/api/v1/login"
|
||||
|
||||
outputs = {
|
||||
"csrf_token": "csrf"
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
|
||||
name = "Login"
|
||||
|
||||
kind = "action"
|
||||
|
||||
showstatus = True
|
||||
|
||||
username = os.environ["RM_USER"]
|
||||
password = os.environ["RM_PASS"]
|
||||
|
||||
keys = [
|
||||
{
|
||||
"keytitle": "email",
|
||||
"keyvalue": username,
|
||||
},
|
||||
{
|
||||
"keytitle": "password",
|
||||
"keyvalue": password,
|
||||
},
|
||||
{
|
||||
"keytitle": "csrf",
|
||||
"keyvalue": {
|
||||
"outputs": "csrf_token",
|
||||
},
|
||||
},
|
||||
{
|
||||
"keytitle": "remember",
|
||||
"keyvalue": 1,
|
||||
},
|
||||
]
|
||||
|
||||
method = "POST"
|
||||
|
||||
path = "https://rentmasseur.com/api/v1/login"
|
||||
|
||||
outputs = {
|
||||
"access_token": "accessToken",
|
||||
"refresh_token": "refreshToken",
|
||||
}
|
||||
|
||||
savedata = [
|
||||
"access_token",
|
||||
"refresh_token",
|
||||
]
|
||||
|
||||
savepath = "/home/david/tokens.json"
|
||||
@@ -0,0 +1,51 @@
|
||||
from . import getcsrf, login
|
||||
|
||||
name = "Token refresh"
|
||||
|
||||
showstatus = True
|
||||
|
||||
method = "GET"
|
||||
|
||||
path = "https://rentmasseur.com/api/v1/refresh/tokens"
|
||||
|
||||
headers = {
|
||||
"Authorization": {
|
||||
"output": "refresh_token",
|
||||
"prefix": "Bearer ",
|
||||
},
|
||||
}
|
||||
|
||||
domainname="rentmasseur.com"
|
||||
|
||||
cookies = {
|
||||
"accessToken": {
|
||||
"output": "access_token",
|
||||
},
|
||||
"refeshToken": {
|
||||
"output": "refresh_token",
|
||||
},
|
||||
}
|
||||
|
||||
loaddata = [
|
||||
"access_token",
|
||||
"refresh_token",
|
||||
]
|
||||
|
||||
savedata = [
|
||||
"access_token",
|
||||
"refresh_token",
|
||||
]
|
||||
|
||||
savepath = "/home/david/tokens.json"
|
||||
|
||||
outputs = {
|
||||
"access_token": "accessToken",
|
||||
"refresh_token": "refreshToken",
|
||||
}
|
||||
|
||||
recoverPolicy = {
|
||||
FileNotFoundError: [
|
||||
getcsrf,
|
||||
login,
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
|
||||
name = "Set availability"
|
||||
|
||||
kind = "action"
|
||||
|
||||
id = "setavailable"
|
||||
|
||||
showstatus = True
|
||||
|
||||
skipstep = {
|
||||
"checkavailable": "validTo >= 3600"
|
||||
}
|
||||
|
||||
availabletime = int(os.environ.get("AVAILABLE_DURATION",3))
|
||||
|
||||
duration = availabletime - 1
|
||||
|
||||
keys = [
|
||||
{
|
||||
"keytitle": "option",
|
||||
"keyvalue": 1,
|
||||
},
|
||||
{
|
||||
"keytitle": "duration",
|
||||
"keyvalue": duration,
|
||||
},
|
||||
]
|
||||
|
||||
method = "PUT"
|
||||
|
||||
path = "https://rentmasseur.com/api/v1/account/dashboard/availability"
|
||||
|
||||
headers = {
|
||||
"Authorization": {
|
||||
"output": "access_token",
|
||||
"prefix": "Bearer ",
|
||||
},
|
||||
}
|
||||
|
||||
outputs = {
|
||||
"available_until": "validTo"
|
||||
}
|
||||
|
||||
prints = {
|
||||
"Available until": "available_until"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
|
||||
name = "Unset Availability"
|
||||
|
||||
kind = "action"
|
||||
|
||||
availabletime = int(os.environ.get("AVAILABLE_DURATION",3))
|
||||
|
||||
duration = availabletime - 5
|
||||
|
||||
|
||||
|
||||
keys = [
|
||||
{
|
||||
"keytitle": "online",
|
||||
"keyvalue": 0,
|
||||
},
|
||||
]
|
||||
|
||||
method = "PUT"
|
||||
|
||||
path = "https://rentmasseur.com/api/v1/account/dashboard/availability"
|
||||
|
||||
#headers = "Authorization:" f"Bearer {access_token}"
|
||||
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
|
||||
unavailabletime = int(os.environ.get("AVAILABLE_DURATION",3))
|
||||
|
||||
duration = unavailabletime - 1
|
||||
|
||||
name = "Set Unavailable"
|
||||
|
||||
kind = "action"
|
||||
|
||||
keys = [
|
||||
{
|
||||
"keytitle": "online",
|
||||
"keyvalue": 2,
|
||||
},
|
||||
{
|
||||
"keytitle": "duration",
|
||||
"keytitle": duration,
|
||||
},
|
||||
]
|
||||
|
||||
method = "PUT"
|
||||
|
||||
path = "https://rentmasseur.com/api/v1/account/dashboard/availability"
|
||||
|
||||
#headers = "Authorization:" f"Bearer {access_token}"
|
||||
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
from . import setavailableoperation
|
||||
|
||||
operations = [
|
||||
setavailableoperation,
|
||||
#setunavailableoperation
|
||||
#setnotsetoperation
|
||||
]
|
||||
|
||||
uptime = datetime.strptime(
|
||||
os.environ["TIME_UPTIME"],
|
||||
"%H:%M"
|
||||
).time
|
||||
|
||||
downtime = datetime.strptime(
|
||||
os.environ["TIME_UPTIME"],
|
||||
"%H:%M"
|
||||
).time
|
||||
|
||||
offline = datetime.strptime(
|
||||
os.environ["TIME_OFFLINE"],
|
||||
"%H:%M"
|
||||
).time
|
||||
@@ -0,0 +1,2 @@
|
||||
from . import login, checkavailable, setavailable
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user