Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
graphex/website
graphex_ssl_certificates
frontend/package-lock.json
/test.py
test.png
Expand Down
6 changes: 5 additions & 1 deletion docs/html/other/changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
<body>

<h1>Changelog</h1>
<p>This page was created to track changes to versions of GraphEx. The changelog was created in v1.4 of GraphEx and only changes starting from that version are tracked here.</p><h2>1.18.0</h2>
<p>This page was created to track changes to versions of GraphEx. The changelog was created in v1.4 of GraphEx and only changes starting from that version are tracked here.</p><h2>2.0.0</h2>
<ul>
<li>Replaces threaded development backend with greenlet based gevent as the production backend server</li>
</ul>
<h2>1.18.0</h2>
<ul>
<li>Pins all used pip module dependencies to current versions</li>
<li>Pins setuptools version for package build</li>
Expand Down
4 changes: 4 additions & 0 deletions docs/markdown/other/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This page was created to track changes to versions of GraphEx. The changelog was created in v1.4 of GraphEx and only changes starting from that version are tracked here.

## 2.0.0

- Replaces threaded development backend with greenlet based gevent as the production backend server

## 1.18.0

- Pins all used pip module dependencies to current versions
Expand Down
6 changes: 6 additions & 0 deletions graphex/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### ### ### ### ###
# Monkey patching should be applied as early as possible
from gevent import monkey
monkey.patch_all()
### ### ### ### ###

from graphex.graph import Graph
from graphex.registry import GraphRegistry
from graphex.runtime import Runtime, GraphRuntime, ForkedThreadRuntime, NodeRuntimeError
Expand Down
55 changes: 36 additions & 19 deletions graphex/__main__.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,8 @@
import os
import re
import shutil
import sys
import typing
from dataclasses import dataclass

from graphex import (
FILE_EXTENSION,
Graph,
GraphConfig,
GraphexLogger,
GraphRegistry,
GraphRuntime,
GraphServer,
vault,
GraphInputValueMetadata,
GraphInventory
)

from cryptography.fernet import InvalidToken
from getpass import getpass

# More imports are farther down this file as needed and not at the top of the file

@dataclass
class Argument:
Expand Down Expand Up @@ -170,6 +152,7 @@ def get_terminal_width(default: int = 120, max_width: int = 240) -> int:
Return a terminal width that is safe to use in headless/non-TTY environments.
Clamps to a sensible range to avoid excessively large widths from environment variables.
"""
import shutil
try:
if sys.stdout.isatty():
width = shutil.get_terminal_size(fallback=(default, 24)).columns
Expand All @@ -189,6 +172,7 @@ def fit_text_to_width(text: str, width: int) -> typing.List[str]:

:returns: A list of strings such that each string is a line of text that fits the given width.
"""
import re
lines = []
line = ""

Expand Down Expand Up @@ -280,6 +264,7 @@ def print_help_and_exit(
:param graph_inputs: Graph inputs available to this help menu.
:param errors: Error messages to print alongside this help menu.
"""
from graphex import ( FILE_EXTENSION, GraphInputValueMetadata, GraphRegistry )

def process_composite(input: GraphInputValueMetadata) -> typing.Union[str, dict]:

Expand Down Expand Up @@ -697,6 +682,7 @@ def decrypt_vault_password_prompt(
:raises Exception: when the number of retries is exceeded for password input
:returns: a tuple of: (the decrypted string, the password used to decrypt the string)
"""
from getpass import getpass
attempts = 0
max_attempts = 3
msg = (
Expand Down Expand Up @@ -740,6 +726,23 @@ def decrypt_vault_password_prompt(
# Get the mode
MODE = args.pop(0)

#########################
# Imports
#########################

# Now import third-party and graphex modules
from cryptography.fernet import InvalidToken #type:ignore
from graphex import (
GraphConfig,
GraphRegistry,
vault,
GraphInventory
)

#########################
# Modes
#########################

# Handle serve
if MODE == "serve":
SERVE_ARGUMENTS = [
Expand All @@ -758,6 +761,11 @@ def decrypt_vault_password_prompt(
if len(args) > 0:
errors.append(f"Unrecognized/extraneous values: {str(args)[1:-1]}")

import re
from graphex import (
GraphServer
)

# Load the config
config: typing.Optional[GraphConfig] = None

Expand Down Expand Up @@ -927,6 +935,13 @@ def decrypt_vault_password_prompt(
]
errors, args = load_arguments(RUN_ARGUMENTS, args)

from graphex import (
Graph,
GraphexLogger,
GraphRuntime,
GraphInputValueMetadata
)

# Load the config
config: typing.Optional[GraphConfig] = None

Expand Down Expand Up @@ -1300,6 +1315,8 @@ def decrypt_value(name: str, config: GraphConfig) -> str:
if HELP.value:
print_help_and_exit(mode=MODE, args=VAULT_ARGUMENTS, graph_inputs=[], errors=[])

from getpass import getpass

## handle user input verification
if not ENCRYPT.value and not DECRYPT.value and not REMOVE.value:
errors.append(
Expand Down
34 changes: 29 additions & 5 deletions graphex/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,19 @@ def __init__(
"""The Flask server."""

logging.getLogger("werkzeug").setLevel(logging.WARNING)
socketio_logger = logging.getLogger("socketio")
engineio_logger = logging.getLogger("engineio")
socketio_logger.setLevel(logging.DEBUG)
engineio_logger.setLevel(logging.DEBUG)

# Use gevent async mode for WebSockets
socketio = SocketIO(
app,
async_mode="gevent",
path="/api/socket.io",
cors_allowed_origins="*",
logger=False,
engineio_logger=False,
logger=socketio_logger,
engineio_logger=engineio_logger
)
self.socketio = socketio
"""The framework for Flask socket support"""
Expand Down Expand Up @@ -887,7 +893,16 @@ def start(self, port: int = 80):
:param port: The port of the webserver.
"""
print(f"GraphEx server starting on all network interfaces at port: {port}")
self.socketio.run(app=self.app, host="0.0.0.0", port=port, allow_unsafe_werkzeug=True, ssl_context=self.ssl_context, debug=False) # type: ignore
cert_path, key_path = self.ssl_context
# socketio.run will use gevent when installed
self.socketio.run(
app=self.app,
host="0.0.0.0",
port=port,
certfile=cert_path,
keyfile=key_path,
debug=False # this arg is for flask dev server debugging
)

#####
# Static methods that return (error message, int status code) for various situations
Expand Down Expand Up @@ -1097,26 +1112,35 @@ def decrypt_config_input(
# If something goes wrong during join, make sure we end the process
# We inform the clients that we are attempting to cleanup the process

# print(f"Cleaning up process for execution context: {context_id}")
print(f"Cleaning up process for execution context: {context_id}", flush=True)
if process.is_alive():
time.sleep(0.9)
print("Process is alive (1), attempting join (1)...", flush=True)
process.join(timeout=10)
if process.is_alive():
print("Process is still alive (2), attempting terminate...", flush=True)
process.terminate()
time.sleep(0.1)
print("attempting join again (2)...", flush=True)
process.join()
time.sleep(1.9)
if process.is_alive():
print("Process is still alive (3), attempting kill...", flush=True)
process.kill()
time.sleep(0.1)
print("attempting join again (3)...", flush=True)
process.join()
time.sleep(1.9)

# release resources on this process
process.close()
running_graph["finished"] = True

# print(f"Finished execution context: {context_id} (exit code {exit_code})")
print(f"Finished execution context: {context_id}", flush=True)
try:
print(f"Exit code: {process.exitcode} for context: {context_id}")
except Exception:
pass

# save this run to the logs
self.saveOutputToFile(graph_name, running_graph["history"])
Expand Down
9 changes: 8 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def read_readme():

setup(
name="mitre-graphex",
version="1.18.0",
version="2.0.0",
author="The MITRE Corporation",
description="Visual programming tool for environment automation.",
long_description=read_readme(),
Expand Down Expand Up @@ -74,6 +74,13 @@ def read_readme():
"Werkzeug==3.1.3",
"wsproto==1.3.2",
# end flask-socketio deps
"gevent==25.9.1",
## start gevent deps
"greenlet==3.2.4",
"zope.event==6.1",
"zope.interface==8.1.1",
# end gevent deps
"gevent-websocket==0.10.1",
"gitPython==3.1.45",
## start gitPython deps
"gitdb==4.0.12",
Expand Down