Skip to content

Commit

Permalink
Move common API code into shared library
Browse files Browse the repository at this point in the history
  • Loading branch information
jk464 committed May 24, 2024
1 parent de3bb49 commit 53d2e92
Show file tree
Hide file tree
Showing 15 changed files with 454 additions and 562 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Added
Contributed by @cognifloyd
* Add support for SSL/TLS to `st2api` and `st2stream` components. #6204
Contributed by @jk464
* Move API code for `st2api`, `st2auth` and `st2stream` components to common library `st2common.openapi`. #6204
Contributed by @jk464

3.8.1 - December 13, 2023
-------------------------
Expand Down
90 changes: 23 additions & 67 deletions st2api/st2api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,83 +16,39 @@
from oslo_config import cfg

from st2api import config as st2api_config
from st2common import log as logging
from st2common.middleware.streaming import StreamingMiddleware
from st2common.middleware.error_handling import ErrorHandlingMiddleware
from st2common.middleware.cors import CorsMiddleware
from st2common.middleware.request_id import RequestIDMiddleware
from st2common.middleware.logging import LoggingMiddleware
from st2common.middleware.instrumentation import RequestInstrumentationMiddleware
from st2common.middleware.instrumentation import ResponseInstrumentationMiddleware
from st2common.router import Router
from st2common.constants.system import VERSION_STRING
from st2common.service_setup import setup as common_setup
from st2common.util import spec_loader
from st2common.openapi import app
from st2api.validation import validate_auth_cookie_is_correctly_configured
from st2api.validation import validate_rbac_is_correctly_configured

LOG = logging.getLogger(__name__)

def setup_app(config={}):
common_setup = {
"register_mq_exchanges": True,
"register_internal_trigger_types": True,
"run_migrations": True,
}

def setup_app(config=None):
config = config or {}

LOG.info("Creating st2api: %s as OpenAPI app.", VERSION_STRING)

is_gunicorn = config.get("is_gunicorn", False)
if is_gunicorn:
# NOTE: We only want to perform this logic in the WSGI worker
st2api_config.register_opts(ignore_errors=True)
capabilities = {
"name": "api",
"listen_host": cfg.CONF.api.host,
"listen_port": cfg.CONF.api.port,
"listen_ssl": cfg.CONF.api.use_ssl,
"type": "active",
}

# This should be called in gunicorn case because we only want
# workers to connect to db, rabbbitmq etc. In standalone HTTP
# server case, this setup would have already occurred.
common_setup(
service="api",
config=st2api_config,
setup_db=True,
register_mq_exchanges=True,
register_signal_handlers=True,
register_internal_trigger_types=True,
run_migrations=True,
service_registry=True,
capabilities=capabilities,
config_args=config.get("config_args", None),
)

# Additional pre-run time checks
validate_auth_cookie_is_correctly_configured()
validate_rbac_is_correctly_configured()

router = Router(
debug=cfg.CONF.api.debug, auth=cfg.CONF.auth.enable, is_gunicorn=is_gunicorn
)
pre_run_checks = [
validate_auth_cookie_is_correctly_configured,
validate_rbac_is_correctly_configured,
]

spec = spec_loader.load_spec("st2common", "openapi.yaml.j2")
transforms = {
"^/api/v1/$": ["/v1"],
"^/api/v1/": ["/", "/v1/"],
"^/api/v1/executions": ["/actionexecutions", "/v1/actionexecutions"],
"^/api/exp/": ["/exp/"],
}
router.add_spec(spec, transforms=transforms)

app = router.as_wsgi

# Order is important. Check middleware for detailed explanation.
app = StreamingMiddleware(app, path_whitelist=["/v1/executions/*/output*"])
app = ErrorHandlingMiddleware(app)
app = CorsMiddleware(app)
app = LoggingMiddleware(app, router)
app = ResponseInstrumentationMiddleware(app, router, service_name="api")
app = RequestIDMiddleware(app)
app = RequestInstrumentationMiddleware(app, router, service_name="api")

return app
path_whitelist = ["/v1/executions/*/output*"]

return app.setup_app(
service_name="api",
app_config=st2api_config,
oslo_cfg=cfg.CONF.api,
pre_run_checks=pre_run_checks,
transforms=transforms,
common_setup_kwargs=common_setup,
path_whitelist=path_whitelist,
config=config,
)
107 changes: 20 additions & 87 deletions st2api/st2api/cmd/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import sys

# NOTE: It's important that we perform monkey patch as early as possible before any other modules
# are important, otherwise SSL support for MongoDB won't work.
# See https://github.com/StackStorm/st2/issues/4832 and https://github.com/gevent/gevent/issues/1016
Expand All @@ -24,15 +21,11 @@

monkey_patch()

import eventlet
from oslo_config import cfg
from eventlet import wsgi

from st2common import log as logging
from st2common.service_setup import setup as common_setup
from st2common.service_setup import teardown as common_teardown
from st2common.service_setup import deregister_service
from st2api import config
from st2common.openapi import api

config.register_opts(ignore_errors=True)

Expand All @@ -43,87 +36,27 @@
__all__ = ["main"]

LOG = logging.getLogger(__name__)
API = "api"

# How much time to give to the request in progress to finish in seconds before killing them
WSGI_SERVER_REQUEST_SHUTDOWN_TIME = 2


def _setup():
capabilities = {
"name": "api",
"listen_host": cfg.CONF.api.host,
"listen_port": cfg.CONF.api.port,
"listen_ssl": cfg.CONF.api.use_ssl,
"type": "active",
def main():
common_setup = {
"register_mq_exchanges": True,
"register_internal_trigger_types": True,
}

common_setup(
service=API,
config=config,
setup_db=True,
register_mq_exchanges=True,
register_signal_handlers=True,
register_internal_trigger_types=True,
service_registry=True,
capabilities=capabilities,
)

# Additional pre-run time checks
validate_auth_cookie_is_correctly_configured()
validate_rbac_is_correctly_configured()


def _run_server():
host = cfg.CONF.api.host
port = cfg.CONF.api.port
use_ssl = cfg.CONF.api.use_ssl

cert_file_path = os.path.realpath(cfg.CONF.api.cert)
key_file_path = os.path.realpath(cfg.CONF.api.key)

if use_ssl and not os.path.isfile(cert_file_path):
raise ValueError('Certificate file "%s" doesn\'t exist' % (cert_file_path))

if use_ssl and not os.path.isfile(key_file_path):
raise ValueError('Private key file "%s" doesn\'t exist' % (key_file_path))

LOG.info(
"(PID=%s) ST2 API is serving on %s://%s:%s.",
os.getpid(),
"https" if use_ssl else "http",
host,
port,
)

max_pool_size = eventlet.wsgi.DEFAULT_MAX_SIMULTANEOUS_REQUESTS
worker_pool = eventlet.GreenPool(max_pool_size)
sock = eventlet.listen((host, port))

if use_ssl:
sock = eventlet.wrap_ssl(
sock, certfile=cert_file_path, keyfile=key_file_path, server_side=True
)

wsgi.server(
sock, app.setup_app(), custom_pool=worker_pool, log=LOG, log_output=False
pre_run_checks = [
validate_auth_cookie_is_correctly_configured,
validate_rbac_is_correctly_configured,
]

api.run(
service_name="api",
app_config=config,
cfg=cfg.CONF.api,
app=app,
log=LOG,
use_custom_pool=False,
log_output=False,
common_setup_kwargs=common_setup,
pre_run_checks=pre_run_checks,
)
return 0


def _teardown():
common_teardown()


def main():
try:
_setup()
return _run_server()
except SystemExit as exit_code:
deregister_service(API)
sys.exit(exit_code)
except Exception:
LOG.exception("(PID=%s) ST2 API quit due to exception.", os.getpid())
return 1
finally:
_teardown()
37 changes: 5 additions & 32 deletions st2api/st2api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,22 @@
from oslo_config import cfg

import st2common.config as common_config
from st2common.constants.system import VERSION_STRING
from st2common.constants.system import DEFAULT_CONFIG_FILE_PATH
from st2common.openapi import config

CONF = cfg.CONF
BASE_DIR = os.path.dirname(os.path.abspath(__file__))


def parse_args(args=None):
cfg.CONF(
args=args,
version=VERSION_STRING,
default_config_files=[DEFAULT_CONFIG_FILE_PATH],
)
config.parse_args(args=args)


def register_opts(ignore_errors=False):
_register_common_opts(ignore_errors=ignore_errors)
_register_app_opts(ignore_errors=ignore_errors)


def _register_common_opts(ignore_errors=False):
common_config.register_opts(ignore_errors=ignore_errors)
config.register_opts(_register_app_opts, ignore_errors=ignore_errors)


def get_logging_config_path():
return cfg.CONF.api.logging
return config.get_logging_config_path(cfg.CONF.api)


def _register_app_opts(ignore_errors=False):
Expand All @@ -76,30 +66,13 @@ def _register_app_opts(ignore_errors=False):
pecan_opts, group="api_pecan", ignore_errors=ignore_errors
)

api_opts = [
cfg.BoolOpt("debug", default=False),
cfg.StrOpt(
"logging",
default="/etc/st2/logging.api.conf",
help="location of the logging.conf file",
),
api_opts = config.get_base_opts("api") + [
cfg.IntOpt(
"max_page_size",
default=100,
help="Maximum limit (page size) argument which can be "
"specified by the user in a query string.",
),
cfg.BoolOpt("use_ssl", default=False, help="Specify to enable SSL / TLS mode"),
cfg.StrOpt(
"cert",
default="/etc/apache2/ssl/mycert.crt",
help='Path to the SSL certificate file. Only used when "use_ssl" is specified.',
),
cfg.StrOpt(
"key",
default="/etc/apache2/ssl/mycert.key",
help='Path to the SSL private key file. Only used when "use_ssl" is specified.',
),
]

common_config.do_register_opts(api_opts, group="api", ignore_errors=ignore_errors)
Loading

0 comments on commit 53d2e92

Please sign in to comment.