Refactoring and linting

This commit is contained in:
adamoutler 2023-02-15 03:26:12 +00:00
parent 2752702d17
commit e3e20a7b07
18 changed files with 134 additions and 120 deletions

View File

@ -13,10 +13,9 @@
"extensions": [ "extensions": [
"pomdtr.secrets", "pomdtr.secrets",
"ms-python.python", "ms-python.python",
"donjayamanne.python-extension-pack",
"ivory-lab.jenkinsfile-support",
"njpwerner.autodocstring", "njpwerner.autodocstring",
"KevinRose.vsc-python-indent" "NicolasVuillamy.vscode-groovy-lint",
"ms-python.pylint"
] ]
} }
} }

View File

@ -1,5 +1,6 @@
{ {
"recommendations": [ "recommendations": [
"pomdtr.secrets" "pomdtr.secrets",
"nicolasvuillamy.vscode-groovy-lint"
] ]
} }

4
.vscode/launch.json vendored
View File

@ -8,7 +8,7 @@
"name": "Start Server", "name": "Start Server",
"type": "python", "type": "python",
"request": "launch", "request": "launch",
"program": "src/aidgaf/aidgaf-server/server.py", "program": "src/aidgaf/aidgaf_server/server.py",
"console": "integratedTerminal", "console": "integratedTerminal",
"justMyCode": true "justMyCode": true
}, },
@ -16,7 +16,7 @@
"name": "IDGAF", "name": "IDGAF",
"type": "python", "type": "python",
"request": "launch", "request": "launch",
"program": "src/aidgaf/aidgaf-server/idgaf.py", "program": "src/aidgaf/aidgaf_server/idgaf.py",
"console": "integratedTerminal", "console": "integratedTerminal",
"justMyCode": true "justMyCode": true
}, },

View File

@ -1,5 +1,10 @@
{ {
"secrets.enabledFolders": [ "secrets.enabledFolders": [
"aidgaf" "aidgaf"
] ],
"files.associations": {
"[Jj]enkinsfile*": "groovy"
},
"python.linting.pylintPath": "/usr/local/python/current/bin/python"
} }

View File

@ -17,6 +17,6 @@ FROM alpine:latest
RUN apk add python3 py3-pip \ RUN apk add python3 py3-pip \
&& pip3 install openai\ && pip3 install openai\
&& mkdir /app && mkdir /app
COPY src/aidgaf /app COPY src/aidgaf /app/aidgaf
EXPOSE 8087 EXPOSE 8087
ENTRYPOINT ["/usr/bin/python","/app/aidgaf-server/__main__.py",">/dev/stdout"] ENTRYPOINT ["/usr/bin/python","/app/aidgaf/aidgaf_server/__main__.py",">/dev/stdout"]

48
Jenkinsfile vendored
View File

@ -1,4 +1,5 @@
#! /bin/groovy #!/bin/groovy
/* groovylint-disable CompileStatic, DuplicateStringLiteral, LineLength */
pipeline { pipeline {
agent { agent {
docker { docker {
@ -9,18 +10,14 @@ pipeline {
} }
} }
stages { stages {
stage('Prepare Environment'){ stage('Prepare Environment') {
steps{ steps {
sh "#!/bin/sh \n" + sh '#!/bin/sh \n' +
'id; apk add docker openrc git' 'id; apk add docker openrc git'
} }
} }
stage('Obtain Source'){ stage('Obtain Source') {
steps { steps {
git branch: 'main', url: 'https://git.adamoutler.com/aoutler/aidgaf-server.git' git branch: 'main', url: 'https://git.adamoutler.com/aoutler/aidgaf-server.git'
} }
@ -28,42 +25,35 @@ pipeline {
stage('Build in docker') { stage('Build in docker') {
steps { steps {
// Get some code from a Git repository // Get some code from a Git repository
sh "#!/bin/sh \n" + sh '#!/bin/sh \n' +
'docker build -t aidgaf .' 'docker build -t aidgaf .'
} }
} }
stage ("setup credentials"){ stage('setup credentials') {
steps{ steps {
withCredentials([ sshUserPrivateKey(credentialsId: 'dockeruserOn192.168.1.115', keyFileVariable: 'sshkey', usernameVariable: 'user')]) { withCredentials([ sshUserPrivateKey(credentialsId: 'dockeruserOn192.168.1.115', keyFileVariable: 'sshkey', usernameVariable: 'user')]) {
sh "#!/bin/sh \n" + sh '#!/bin/sh \n' +
'set +e; docker stop aidgaf-server||echo machine stopped; docker rm aidgaf-server||echo machine does not exist; set -e' 'set +e; docker stop aidgaf-server||echo machine stopped; docker rm aidgaf-server||echo machine does not exist; set -e'
} }
} }
} }
stage ('export docker container'){ stage('export docker container') {
steps { steps {
sh '#!/bin/sh \n' +
sh "#!/bin/sh \n" +
'set +e; docker stop aidgaf-server||echo machine stopped; docker rm aidgaf-server||echo machine does not exist; set -e' 'set +e; docker stop aidgaf-server||echo machine stopped; docker rm aidgaf-server||echo machine does not exist; set -e'
withCredentials([ withCredentials([
string(credentialsId: 'OpenAI-API-Token', variable: 'OPEN_AI_TOKEN'), string(credentialsId: 'OpenAI-API-Token', variable: 'OPEN_AI_TOKEN'),
string(credentialsId: 'PapaHashingSecret', variable: 'PAPA_HASH'), string(credentialsId: 'PapaHashingSecret', variable: 'PAPA_HASH'),
sshUserPrivateKey(credentialsId: 'dockeruserOn192.168.1.115', keyFileVariable: 'sshkey', usernameVariable: 'user') string(credentialsId: 'PapaAsyncUrl', variable: 'ASYNC_URL'),
sshUserPrivateKey(credentialsId: 'dockeruserOn192.168.1.115', keyFileVariable: 'sshkey')
]) { ]) {
sh "#!/bin/sh \n" + sh '#!/bin/sh \n' +
'mkdir -p ~/.ssh; cp "${sshkey}" ~/.ssh/id_rsa' 'mkdir -p ~/.ssh; cp "$sshkey" ~/.ssh/id_rsa'
sh "#!/bin/sh \n" + sh '#!/bin/sh \n' +
'docker run --name=aidgaf-server -eSERVERPORT=8087 -eHOSTNAME=0.0.0.0 -eHASHKEY="${PAPA_HASH}" -eAPIKEY="${OPEN_AI_TOKEN}" -p8087:8087 -d --restart=always aidgaf' /* groovylint-disable-next-line GStringExpressionWithinString */
'docker run --name=aidgaf-server -eSERVERPORT=8087 -eHOSTNAME=0.0.0.0 -eHASHKEY="${PAPA_HASH}" -eAPIKEY="${OPEN_AI_TOKEN}" -eASYNC_METHOD="PATCH" -eASYNC_URL="${} -p8087:8087 -d --restart=always aidgaf'
} }
} }
} }
// To run Maven on a Windows agent, use
// bat "mvn -Dmaven.test.failure.ignore=true clean package"
} }
} }

View File

@ -1,7 +0,0 @@
""" aidgaf-server. """
""" The name of the package"""
name = 'aidgaf'
""" The version of the package"""
version='0.1.0'

View File

@ -1,5 +0,0 @@
""" aidgaf-server entry point. """
import server
""" The main entry point for the aidgaf-server package."""
server.main()

View File

@ -1,4 +0,0 @@
"""Constants for the aidgaf-server package."""
""" The encoding to use for strings. """
UTF8="utf-8"

View File

@ -0,0 +1,7 @@
""" aidgaf-server. """
# The name of the package
name = 'aidgaf_server' # pylint: disable=C0103
# The version of the package
version = '0.1.0' # pylint: disable=C0103

View File

@ -0,0 +1,5 @@
""" aidgaf-server entry point. """
import server
# The main entry point for the aidgaf-server package.
server.main()

View File

@ -0,0 +1,10 @@
"""Constants for the aidgaf-server package."""
UTF8="utf-8"
""" The encoding to use for strings. """
OPENAI_TIMEOUT=30
""" The timeout for OpenAI requests. """
REPORTING_TIMEOUT=10
""" The timeout for async reporting requests. """

View File

@ -1,6 +1,8 @@
""" This module contains the function to calculate the hash of a value and a secret."""
import hashlib import hashlib
def calculate_hash(value:bytes, secret:bytes)->str:
def calculate_hash(value: bytes, secret: bytes) -> str:
""" This function calculates the hash of a value and a secret. """ This function calculates the hash of a value and a secret.
It is used to verify that the message is from the server. The hash It is used to verify that the message is from the server. The hash
is calculated using the SHA512 algorithm. The hash is returned as a is calculated using the SHA512 algorithm. The hash is returned as a
@ -13,6 +15,6 @@ def calculate_hash(value:bytes, secret:bytes)->str:
returns: returns:
The hash of the value and secret. This is a hex string. The hash of the value and secret. This is a hex string.
""" """
m = hashlib.sha512() sha512 = hashlib.sha512()
m.update(b"".join([value,secret])) sha512.update(b"".join([value, secret]))
return m.hexdigest() return sha512.hexdigest()

View File

@ -1,61 +1,61 @@
""" This file contains the logic for the IDGAF server. It can be used to make a stand-alone """ This file contains the logic for the IDGAF server. It can be used to make a stand-alone
IDGAF server, or it can be used as a module in a larger application. IDGAF server, or it can be used as a module in a larger application.
""" """
from datetime import datetime
import json import json
import random import random
import security
import re
import requests import requests
from time import time
from datetime import datetime
import settings import settings
from const import OPENAI_TIMEOUT
""" The URL for the OpenAI API. """
URL = "https://api.openai.com/v1/completions" URL = "https://api.openai.com/v1/completions"
""" The URL for the OpenAI API. """
""" The data to send to OpenAI. """
DATA = {"model": settings.OPEN_AI_COMPLETION_MODEL, DATA = {"model": settings.OPEN_AI_COMPLETION_MODEL,
"prompt": settings.PROMPTS[0], "prompt": settings.PROMPTS[0],
"temperature": settings.TEMPERATURE, "temperature": settings.TEMPERATURE,
"max_tokens": settings.OPEN_AI_MAX_TOKENS "max_tokens": settings.OPEN_AI_MAX_TOKENS
} }
""" The data to send to OpenAI. """
""" The headers to send to OpenAI. """
request_headers = {"Authorization": "Bearer " + request_headers = {"Authorization": "Bearer " +
settings.APIKEY, "Content-Type": "application/json"} settings.APIKEY, "Content-Type": "application/json"}
""" The headers to send to OpenAI. """
def get_response_base_object(text:str) -> dict:
def get_response_base_object(text: str) -> dict:
""" This is used to create the response object for the server. """ This is used to create the response object for the server.
Parameters: Parameters:
text: The text to return to the client. text: The text to return to the client.
Returns: Returns:
A dictionary containing the response object. A dictionary containing the response object.
""" """
resultObject = {} result_object = {}
resultObject["message"] = {} result_object["message"] = {}
resultObject["service"] = "AIDGAF Server" result_object["service"] = "AIDGAF Server"
resultObject["message"]["data"] = {} result_object["message"]["data"] = {}
resultObject["message"]["data"]["resultObject"] = text result_object["message"]["data"]["resultObject"] = text
resultObject["timestamp"] = datetime.utcnow().timestamp() result_object["timestamp"] = datetime.utcnow().timestamp()
return resultObject return result_object
def parse_idgaf_request(command)->[int, dict]: def parse_idgaf_request(idgaf_command) -> [int, dict]:
""" This function handles the IDGAF command. It will return a response object. """ This function handles the IDGAF command. It will return a response object.
Parameters: Parameters:
command: The command object received from the client. command: The command object received from the client.
Returns: Returns:
A tuple containing the status code and the response object. A tuple containing the status code and the response object.
""" """
the_data = get_prompt(command) the_data = get_prompt(idgaf_command)
response = get_gpt_response(the_data) response = get_gpt_response(the_data)
try: try:
response_text = response.json()['choices'][0]['text'].strip() response_text = response.json()['choices'][0]['text'].strip()
except (KeyError): except KeyError:
response_text = gpt_response.text response_text = response.text
obj = get_response_base_object(response_text) obj = get_response_base_object(response_text)
return [response.status_code, obj] return [response.status_code, obj]
def get_gpt_response(data) -> requests.Response: def get_gpt_response(data) -> requests.Response:
""" This function communicates with OpenAI and returns a response object. """ This function communicates with OpenAI and returns a response object.
Parameters: Parameters:
@ -63,11 +63,14 @@ def get_gpt_response(data) -> requests.Response:
Returns: Returns:
The response object from OpenAI. The response object from OpenAI.
""" """
gpt_response = requests.post(URL, json=data, headers=request_headers) gpt_response = requests.post(
url=URL, json=data, headers=request_headers, timeout=OPENAI_TIMEOUT)
return gpt_response return gpt_response
def get_prompt(command)->dict:
""" Selects a prompt from the PROMPTS list and replaces the USERNAME placeholder with the username. def get_prompt(command) -> dict:
""" Selects a prompt from the PROMPTS list and replaces the USERNAME placeholder
with the username.
Parameters: Parameters:
command: The command object received from the client. command: The command object received from the client.
Returns: Returns:
@ -83,9 +86,14 @@ def get_prompt(command)->dict:
return the_data return the_data
# This function is for testing the IDGAF capabilities without a server.
if __name__ == "__main__": if __name__ == "__main__":
""" This function is for testing the IDGAF capabilities without a server. """ INPUT = '''{"service":"papa","message":
value = '{"service":"papa","message":{"command":"aidgaf","data":{"username":"AdamOutler"},"timestamp":1675725191},"hash":"1bc73914478835d03f9ebdfb46328321d2bb656647e2876d6f162cc1860607fcfca8d825c48e390a6a254ee0835c8a4fe5f9a25795a3a0880ae5a23e9c132cf2"}' {"command":"aidgaf","data":{"username":"AdamOutler"},
command = json.loads(value) "timestamp":1675725191},
[code, result] = parse_idgaf_request(command) "hash":"1bc73914478835d03f9ebdfb46328321d2bb656647e28
76d6f162cc1860607fcfca8d825c48e390a6a254ee0835c8a4fe5f
9a25795a3a0880ae5a23e9c132cf2"}'''
test_command = json.loads(INPUT)
[code, result] = parse_idgaf_request(test_command)
print(result) print(result)

View File

@ -1,18 +1,18 @@
"""This module contains the merge_dict function.""" """This module contains the merge_dict function."""
from functools import reduce
def merge_dict_no_overwrite(base_dictionary, other_dictionary, location=None)->dict: def merge_dict_no_overwrite(base_dictionary, other_dictionary, location=None)->dict:
""" This function merges two dictionaries. If the same key exists in both dictionaries, """ This function merges two dictionaries. If the same key exists in both dictionaries,
the value from the other_dictionary will be used. This function will recurse into nested the value from the other_dictionary will be used. This function will recurse into nested
dictionaries. If the same key exists in both dictionaries, and the value is a dictionary, dictionaries. If the same key exists in both dictionaries, and the value is a dictionary,
the function will recurse into the nested dictionary. If the same key exists in both dictionaries, the function will recurse into the nested dictionary. If the same key exists in both
and the value is not a dictionary, the value from the base_dictionary will be used. dictionaries, and the value is not a dictionary, the value from the base_dictionary will
be used.
Parameters: Parameters:
base_dictionary: The dictionary to merge the other_dictionary into. base_dictionary: The dictionary to merge the other_dictionary into.
other_dictionary: The dictionary to merge keys from. if the same key exists in both dictionaries, other_dictionary: The dictionary to merge keys from. if the same key exists in both
the value from the base_dictionary will be used. dictionaries, the value from the base_dictionary will be used.
location: leave blank. This is used to track the location of the key in the dictionary. location: leave blank. This is used to track the location of the key in the dictionary.
Returns: Returns:
The merged dictionary. The merged dictionary.
@ -29,5 +29,3 @@ def merge_dict_no_overwrite(base_dictionary, other_dictionary, location=None)->d
else: else:
base_dictionary[key] = other_dictionary[key] base_dictionary[key] = other_dictionary[key]
return base_dictionary return base_dictionary

View File

@ -13,9 +13,9 @@ def perform_hash_checks(str_request)->bool:
str_request: The request body, as a string. str_request: The request body, as a string.
Returns: Returns:
True if the message is valid, False otherwise.""" True if the message is valid, False otherwise."""
hash = get_message_hash(str_request) my_hash = get_message_hash(str_request)
if hash not in str_request: if my_hash not in str_request:
print("Error: hash not match") print("Error: hash not match")
return False return False
if not verify_message_time(str_request): if not verify_message_time(str_request):
@ -32,12 +32,12 @@ def get_message_hash(json_command) -> str:
The hash of the message, as a string. """ The hash of the message, as a string. """
strip1 = re.sub(".*\"message\":", "", json_command, 1) strip1 = re.sub(".*\"message\":", "", json_command, 1)
if ("\"hash\":" in strip1): if "\"hash\":" in strip1:
strip2 = re.sub(',\"hash\":.*', '', strip1) strip2 = re.sub(',\"hash\":.*', '', strip1)
else: else:
strip2 = strip1[:-1] strip2 = strip1[:-1]
json_value = bytes(strip2, UTF8) json_value = bytes(strip2, UTF8)
if (settings.HASHKEY is not None): if settings.HASHKEY is not None:
hash_value = hash_calculator.calculate_hash( hash_value = hash_calculator.calculate_hash(
json_value, settings.HASHKEY) json_value, settings.HASHKEY)
return hash_value return hash_value

View File

@ -1,16 +1,15 @@
""" aidgaf-server - A simple server to handle requests from the aidgaf client. """ aidgaf-server - A simple server to handle requests from the aidgaf client.
This server will communicate with OpenAI to generate responses to the client.""" This server will communicate with OpenAI to generate responses to the client."""
from http.server import BaseHTTPRequestHandler, HTTPServer
import json import json
import random
import threading import threading
import requests import requests
import merge
from http.server import BaseHTTPRequestHandler, HTTPServer import merge
import idgaf import idgaf
import settings import settings
import security import security
from const import UTF8 from const import UTF8, REPORTING_TIMEOUT
default_request_body = b'{"message":{"command":"aidgaf","data":{"username":"AdamOutler"}}}' default_request_body = b'{"message":{"command":"aidgaf","data":{"username":"AdamOutler"}}}'
request_headers = {} request_headers = {}
@ -23,13 +22,18 @@ class IDGAFServer(BaseHTTPRequestHandler):
self.send_response(418) self.send_response(418)
self.send_header("Content-type", "text/html") self.send_header("Content-type", "text/html")
self.end_headers() self.end_headers()
self.wfile.write(bytes("", UTF8)) self.wfile.write(bytes("HTTP 408\nI'm a teapot", UTF8))
self.close_connection
def do_PATCH(self): def do_PATCH(self):
""" This function handles PATCH requests. """ """ This function handles PATCH requests. """
body = self.get_body().decode(UTF8) body = self.get_body().decode(UTF8)
if not perform_sanity_checks(body): check_result=perform_sanity_checks(body)
if check_result:
self.send_response(403) self.send_response(403)
self.end_headers()
self.wfile.write(bytes(check_result, UTF8))
self.close_connection
return return
command = json.loads(body) command = json.loads(body)
self.do_request_handling(command) self.do_request_handling(command)
@ -73,19 +77,18 @@ class IDGAFServer(BaseHTTPRequestHandler):
if (code != 200): if (code != 200):
return return
if (settings.ASYNC_METHOD == "POST"): if (settings.ASYNC_METHOD == "POST"):
requests.post(settings.ASYNC_URL, data=data, headers=request_headers) requests.post(settings.ASYNC_URL, data=data, headers=request_headers,
timeout=REPORTING_TIMEOUT)
elif (settings.ASYNC_METHOD == "PUT"): elif (settings.ASYNC_METHOD == "PUT"):
requests.put(settings.ASYNC_URL, data=data, headers=request_headers) requests.put(settings.ASYNC_URL, data=data, headers=request_headers,
timeout=REPORTING_TIMEOUT)
elif (settings.ASYNC_METHOD == "PATCH"): elif (settings.ASYNC_METHOD == "PATCH"):
resp=requests.patch(settings.ASYNC_URL, data=data, headers=request_headers) resp=requests.patch(settings.ASYNC_URL, data=data, headers=request_headers,
timeout=REPORTING_TIMEOUT)
print(str(resp)+resp.text) print(str(resp)+resp.text)
# gpt_response = requests.post(URL, json=data, headers=request_headers) # gpt_response = requests.post(URL, json=data, headers=request_headers)
def getGPTResponse(self, command) -> [int, str]: def getGPTResponse(self, command) -> [int, str]:
@ -124,9 +127,10 @@ class IDGAFServer(BaseHTTPRequestHandler):
self.end_headers() self.end_headers()
print("sending:"+body) print("sending:"+body)
self.wfile.write(bytes(body, UTF8)) self.wfile.write(bytes(body, UTF8))
self.close_connection
def perform_sanity_checks(str_request) -> bool: def perform_sanity_checks(str_request) -> str:
""" Performs a hash check on the message, and verifies the timestamp is valid. """ Performs a hash check on the message, and verifies the timestamp is valid.
If either check fails, return False. Otherwise, return True. If either check fails, return False. Otherwise, return True.
Parameters: Parameters:
@ -135,13 +139,13 @@ def perform_sanity_checks(str_request) -> bool:
True if the message is valid, False otherwise. """ True if the message is valid, False otherwise. """
if settings.HASHKEY is not None: if settings.HASHKEY is not None:
hash = security.get_message_hash(str_request) hash = security.get_message_hash(str_request)
if hash not in str_request: if settings.HASHKEY and hash not in str_request:
print("Error: hash not match") print("Error: hash not match")
return False return "Error: hash not match"
if not security.verify_message_time(str_request): if settings.HASHKEY and not security.verify_message_time(str_request):
print("Error: timestamp expired") print("Error: timestamp expired")
return False return "Error: timestamp expired"
return True return ""
def main(): def main():

View File

@ -42,7 +42,8 @@ OPEN_AI_COMPLETION_MODEL = "text-davinci-003"
TEMPERATURE = 0.7 TEMPERATURE = 0.7
""" The hash key for the server. Leave this blank if you don't want to use it. """ """ The hash key for the server. Leave this blank if you don't want to use it. """
HASHKEY = bytes(os.getenv('HASHKEY'), HASHKEY = bytes(os.getenv('HASHKEY') or "",UTF8) # shared secret for hmac of message
UTF8) # shared secret for hmac of message if (HASHKEY == ""):
HASHKEY=None
""" The maximum age of a message in seconds. Only used if HASHKEY is set.""" """ The maximum age of a message in seconds. Only used if HASHKEY is set."""
MAX_MESSAGE_AGE = 600 MAX_MESSAGE_AGE = 600