stoobly-agent 1.9.11__py3-none-any.whl → 1.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/api/__init__.py +4 -20
- stoobly_agent/app/api/configs_controller.py +3 -3
- stoobly_agent/app/cli/decorators/exec.py +1 -1
- stoobly_agent/app/cli/helpers/handle_config_update_service.py +4 -0
- stoobly_agent/app/cli/helpers/shell.py +0 -10
- stoobly_agent/app/cli/intercept_cli.py +40 -7
- stoobly_agent/app/cli/scaffold/app_command.py +4 -0
- stoobly_agent/app/cli/scaffold/app_config.py +21 -3
- stoobly_agent/app/cli/scaffold/app_create_command.py +109 -2
- stoobly_agent/app/cli/scaffold/constants.py +14 -0
- stoobly_agent/app/cli/scaffold/docker/constants.py +4 -6
- stoobly_agent/app/cli/scaffold/docker/service/builder.py +19 -4
- stoobly_agent/app/cli/scaffold/docker/workflow/builder.py +0 -18
- stoobly_agent/app/cli/scaffold/docker/workflow/command_decorator.py +24 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/decorators_factory.py +7 -2
- stoobly_agent/app/cli/scaffold/docker/workflow/detached_decorator.py +42 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/local_decorator.py +26 -0
- stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +9 -10
- stoobly_agent/app/cli/scaffold/docker/workflow/reverse_proxy_decorator.py +5 -8
- stoobly_agent/app/cli/scaffold/service_config.py +144 -21
- stoobly_agent/app/cli/scaffold/service_create_command.py +11 -2
- stoobly_agent/app/cli/scaffold/service_dependency.py +51 -0
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/build/mock/docker-compose.yml +16 -6
- stoobly_agent/app/cli/scaffold/templates/app/build/record/docker-compose.yml +16 -6
- stoobly_agent/app/cli/scaffold/templates/app/build/test/docker-compose.yml +16 -6
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/mock/docker-compose.yml +16 -10
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/record/docker-compose.yml +16 -10
- stoobly_agent/app/cli/scaffold/templates/app/entrypoint/test/docker-compose.yml +16 -10
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml +2 -1
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml +6 -3
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml +6 -4
- stoobly_agent/app/cli/scaffold/templates/constants.py +4 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/.Dockerfile.cypress +22 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/cypress/test/.docker-compose.test.yml +19 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.Dockerfile.playwright +33 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.docker-compose.test.yml +18 -0
- stoobly_agent/app/cli/scaffold/templates/plugins/playwright/test/.entrypoint.sh +11 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/docker-compose.yml +17 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/docker-compose.yml +17 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/test/docker-compose.yml +17 -0
- stoobly_agent/app/cli/scaffold/workflow_create_command.py +0 -1
- stoobly_agent/app/cli/scaffold/workflow_namesapce.py +8 -2
- stoobly_agent/app/cli/scaffold/workflow_run_command.py +1 -1
- stoobly_agent/app/cli/scaffold_cli.py +77 -83
- stoobly_agent/app/proxy/handle_record_service.py +12 -3
- stoobly_agent/app/proxy/handle_replay_service.py +14 -2
- stoobly_agent/app/proxy/intercept_settings.py +11 -7
- stoobly_agent/app/proxy/mock/eval_fixtures_service.py +33 -2
- stoobly_agent/app/proxy/record/upload_request_service.py +2 -2
- stoobly_agent/app/proxy/replay/replay_request_service.py +3 -0
- stoobly_agent/app/proxy/run.py +3 -28
- stoobly_agent/app/proxy/utils/allowed_request_service.py +3 -2
- stoobly_agent/app/proxy/utils/minimize_headers.py +47 -0
- stoobly_agent/app/proxy/utils/publish_change_service.py +5 -4
- stoobly_agent/app/proxy/utils/strategy.py +16 -0
- stoobly_agent/app/settings/__init__.py +9 -3
- stoobly_agent/app/settings/data_rules.py +25 -1
- stoobly_agent/app/settings/intercept_settings.py +5 -2
- stoobly_agent/app/settings/types/__init__.py +0 -1
- stoobly_agent/app/settings/ui_settings.py +5 -5
- stoobly_agent/cli.py +41 -16
- stoobly_agent/config/constants/custom_headers.py +1 -0
- stoobly_agent/config/constants/env_vars.py +4 -3
- stoobly_agent/config/constants/record_strategy.py +6 -0
- stoobly_agent/config/settings.yml.sample +2 -3
- stoobly_agent/lib/logger.py +15 -5
- stoobly_agent/test/app/cli/intercept/intercept_configure_test.py +231 -1
- stoobly_agent/test/app/cli/scaffold/cli_invoker.py +3 -2
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/app/proxy/mock/eval_fixtures_service_test.py +14 -2
- stoobly_agent/test/app/proxy/utils/minimize_headers_test.py +342 -0
- {stoobly_agent-1.9.11.dist-info → stoobly_agent-1.10.0.dist-info}/METADATA +2 -1
- {stoobly_agent-1.9.11.dist-info → stoobly_agent-1.10.0.dist-info}/RECORD +78 -62
- {stoobly_agent-1.9.11.dist-info → stoobly_agent-1.10.0.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.9.11.dist-info → stoobly_agent-1.10.0.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.9.11.dist-info → stoobly_agent-1.10.0.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,14 @@
|
|
1
1
|
services:
|
2
2
|
stoobly_ui.service:
|
3
|
-
command: --ui-port 4200
|
3
|
+
command: --proxyless --ui-host local.stoobly.com --ui-port 4200
|
4
4
|
extends:
|
5
5
|
file: ../.docker-compose.base.yml
|
6
6
|
service: stoobly_ui.base
|
7
|
-
|
7
|
+
networks:
|
8
|
+
app.egress:
|
9
|
+
aliases:
|
10
|
+
- local.stoobly.com
|
8
11
|
ports:
|
9
12
|
- '${APP_UI_PORT}:4200'
|
10
13
|
profiles:
|
11
|
-
-
|
14
|
+
- ${WORKFLOW_NAME}
|
@@ -1,12 +1,14 @@
|
|
1
1
|
services:
|
2
2
|
stoobly_ui.service:
|
3
|
-
command: --ui-port 4200
|
3
|
+
command: --proxyless --ui-host local.stoobly.com --ui-port 4200
|
4
4
|
extends:
|
5
5
|
file: ../.docker-compose.base.yml
|
6
6
|
service: stoobly_ui.base
|
7
|
-
|
7
|
+
networks:
|
8
|
+
app.egress:
|
9
|
+
aliases:
|
10
|
+
- local.stoobly.com
|
8
11
|
ports:
|
9
12
|
- '${APP_UI_PORT}:4200'
|
10
13
|
profiles:
|
11
|
-
-
|
12
|
-
|
14
|
+
- ${WORKFLOW_NAME}
|
@@ -13,6 +13,7 @@ CORE_RECORD_WORKFLOW = 'record'
|
|
13
13
|
|
14
14
|
CUSTOM_BUILD = os.path.join('bin', 'build')
|
15
15
|
CUSTOM_CONFIGURE = os.path.join('bin', 'configure')
|
16
|
+
CUSTOM_DOCKER_COMPOSE = 'docker-compose.yml'
|
16
17
|
CUSTOM_INIT = os.path.join('bin', 'init')
|
17
18
|
CUSTOM_FIXTURES = 'fixtures.yml'
|
18
19
|
CUSTOM_LIFECYCLE_HOOKS = os.path.join('lifecycle_hooks.py')
|
@@ -28,6 +29,7 @@ MOCK_WORKFLOW_MAINTAINED_FILES = [
|
|
28
29
|
MOCK_WORKFLOW_CUSTOM_FILES = [
|
29
30
|
CUSTOM_BUILD,
|
30
31
|
CUSTOM_CONFIGURE,
|
32
|
+
CUSTOM_DOCKER_COMPOSE,
|
31
33
|
CUSTOM_FIXTURES,
|
32
34
|
CUSTOM_INIT,
|
33
35
|
CUSTOM_LIFECYCLE_HOOKS,
|
@@ -42,6 +44,7 @@ RECORD_WORKFLOW_MAINTAINED_FILES = [
|
|
42
44
|
RECORD_WORKFLOW_CUSTOM_FILES = [
|
43
45
|
CUSTOM_BUILD,
|
44
46
|
CUSTOM_CONFIGURE,
|
47
|
+
CUSTOM_DOCKER_COMPOSE,
|
45
48
|
CUSTOM_INIT,
|
46
49
|
CUSTOM_LIFECYCLE_HOOKS,
|
47
50
|
]
|
@@ -54,6 +57,7 @@ TEST_WORKFLOW_MAINTAINED_FILES = [
|
|
54
57
|
TEST_WORKFLOW_CUSTOM_FILES = [
|
55
58
|
CUSTOM_BUILD,
|
56
59
|
CUSTOM_CONFIGURE,
|
60
|
+
CUSTOM_DOCKER_COMPOSE,
|
57
61
|
CUSTOM_FIXTURES,
|
58
62
|
CUSTOM_INIT,
|
59
63
|
CUSTOM_LIFECYCLE_HOOKS,
|
@@ -0,0 +1,22 @@
|
|
1
|
+
ARG CYPRESS_IMAGE
|
2
|
+
FROM ${CYPRESS_IMAGE}
|
3
|
+
|
4
|
+
# Change user id of stoobly user to that of host's user id
|
5
|
+
ARG USER_ID
|
6
|
+
ARG USER_NAME=stoobly
|
7
|
+
RUN set -eux; \
|
8
|
+
# Check if a user with this UID exists
|
9
|
+
if getent passwd "${USER_ID}" > /dev/null; then \
|
10
|
+
EXISTING_USER="$(getent passwd "${USER_ID}" | cut -d: -f1)"; \
|
11
|
+
if [ "$EXISTING_USER" != "${USER_NAME}" ]; then \
|
12
|
+
usermod -l "${USER_NAME}" "$EXISTING_USER"; \
|
13
|
+
usermod -d "/home/${USER_NAME}" -m "${USER_NAME}"; \
|
14
|
+
fi; \
|
15
|
+
else \
|
16
|
+
useradd -u "${USER_ID}" -m "${USER_NAME}"; \
|
17
|
+
fi
|
18
|
+
|
19
|
+
USER stoobly
|
20
|
+
WORKDIR /home/stoobly
|
21
|
+
|
22
|
+
RUN npx cypress install # Install Cypress binary into image
|
@@ -0,0 +1,19 @@
|
|
1
|
+
services:
|
2
|
+
entrypoint.cypress:
|
3
|
+
build:
|
4
|
+
args:
|
5
|
+
CYPRESS_IMAGE: ${CYPRESS_IMAGE:-cypress/browsers:22.11.0}
|
6
|
+
USER_ID: ${USER_ID}
|
7
|
+
context: ./
|
8
|
+
dockerfile: ./.Dockerfile.cypress
|
9
|
+
command: npx cypress run --project .
|
10
|
+
depends_on:
|
11
|
+
entrypoint.configure:
|
12
|
+
condition: service_completed_successfully
|
13
|
+
networks:
|
14
|
+
- "app.ingress"
|
15
|
+
profiles:
|
16
|
+
- ${WORKFLOW_NAME}
|
17
|
+
user: stoobly
|
18
|
+
volumes:
|
19
|
+
- ${CONTEXT_DIR}:/home/stoobly
|
@@ -0,0 +1,33 @@
|
|
1
|
+
ARG PLAYWRIGHT_IMAGE
|
2
|
+
FROM ${PLAYWRIGHT_IMAGE}
|
3
|
+
|
4
|
+
RUN set -eux; \
|
5
|
+
apt-get update; \
|
6
|
+
apt-get install -y --no-install-recommends ca-certificates wget; \
|
7
|
+
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
|
8
|
+
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.16/gosu-$dpkgArch"; \
|
9
|
+
chmod +x /usr/local/bin/gosu; \
|
10
|
+
gosu --version;
|
11
|
+
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
|
12
|
+
RUN npx playwright install --with-deps
|
13
|
+
|
14
|
+
# Change user id of stoobly user to that of host's user id
|
15
|
+
ARG USER_ID
|
16
|
+
ARG USER_NAME=stoobly
|
17
|
+
RUN set -eux; \
|
18
|
+
# Check if a user with this UID exists
|
19
|
+
if getent passwd "${USER_ID}" > /dev/null; then \
|
20
|
+
EXISTING_USER="$(getent passwd "${USER_ID}" | cut -d: -f1)"; \
|
21
|
+
if [ "$EXISTING_USER" != "${USER_NAME}" ]; then \
|
22
|
+
usermod -l "${USER_NAME}" "$EXISTING_USER"; \
|
23
|
+
usermod -d "/home/${USER_NAME}" -m "${USER_NAME}"; \
|
24
|
+
fi; \
|
25
|
+
else \
|
26
|
+
useradd -u "${USER_ID}" -m "${USER_NAME}"; \
|
27
|
+
fi
|
28
|
+
|
29
|
+
WORKDIR /home/stoobly
|
30
|
+
|
31
|
+
COPY .entrypoint.sh /usr/local/bin/entrypoint.sh
|
32
|
+
RUN chmod +x /usr/local/bin/entrypoint.sh
|
33
|
+
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
services:
|
2
|
+
entrypoint.playwright:
|
3
|
+
build:
|
4
|
+
args:
|
5
|
+
PLAYWRIGHT_IMAGE: ${PLAYWRIGHT_IMAGE:-mcr.microsoft.com/playwright:v1.46.1-jammy}
|
6
|
+
USER_ID: ${USER_ID}
|
7
|
+
context: ./
|
8
|
+
dockerfile: ./.Dockerfile.playwright
|
9
|
+
command: npx playwright test --reporter dot
|
10
|
+
depends_on:
|
11
|
+
entrypoint.configure:
|
12
|
+
condition: service_completed_successfully
|
13
|
+
networks:
|
14
|
+
- "app.ingress"
|
15
|
+
profiles:
|
16
|
+
- ${WORKFLOW_NAME}
|
17
|
+
volumes:
|
18
|
+
- ${CONTEXT_DIR}:/home/stoobly
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
set -e
|
3
|
+
|
4
|
+
# Copy certs and update trust store
|
5
|
+
if [ -d "/home/stoobly/.stoobly/certs" ]; then
|
6
|
+
cp /home/stoobly/.stoobly/certs/*.crt /usr/local/share/ca-certificates/ || true
|
7
|
+
update-ca-certificates
|
8
|
+
fi
|
9
|
+
|
10
|
+
# Execute the CMD from Dockerfile or passed command
|
11
|
+
exec gosu stoobly "$@"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Define custom services here
|
2
|
+
#
|
3
|
+
# 1. Uncomment the following
|
4
|
+
# 2. Replace <SERVICE-NAME> with the name of the service, this can be found in ../config.yml
|
5
|
+
# 3. Extend as needed
|
6
|
+
#
|
7
|
+
# To learn more, see https://docs.stoobly.com/core-concepts/scaffold
|
8
|
+
|
9
|
+
#services:
|
10
|
+
# <SERVICE-NAME>.<CUSTOM-SERVICE-NAME>:
|
11
|
+
# depends_on:
|
12
|
+
# <SERVICE-NAME>.configure:
|
13
|
+
# condition: service_completed_successfully
|
14
|
+
# networks:
|
15
|
+
# app.ingress: {}
|
16
|
+
# profiles:
|
17
|
+
# - ${WORKFLOW_NAME}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Define custom services here
|
2
|
+
#
|
3
|
+
# 1. Uncomment the following
|
4
|
+
# 2. Replace <SERVICE-NAME> with the name of the service, this can be found in ../config.yml
|
5
|
+
# 3. Extend as needed
|
6
|
+
#
|
7
|
+
# To learn more, see https://docs.stoobly.com/core-concepts/scaffold
|
8
|
+
|
9
|
+
#services:
|
10
|
+
# <SERVICE-NAME>.<CUSTOM-SERVICE-NAME>:
|
11
|
+
# depends_on:
|
12
|
+
# <SERVICE-NAME>.configure:
|
13
|
+
# condition: service_completed_successfully
|
14
|
+
# networks:
|
15
|
+
# app.egress: {}
|
16
|
+
# profiles:
|
17
|
+
# - ${WORKFLOW_NAME}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Define custom services here
|
2
|
+
#
|
3
|
+
# 1. Uncomment the following
|
4
|
+
# 2. Replace <SERVICE-NAME> with the name of the service, this can be found in ../config.yml
|
5
|
+
# 3. Extend as needed
|
6
|
+
#
|
7
|
+
# To learn more, see https://docs.stoobly.com/core-concepts/scaffold
|
8
|
+
|
9
|
+
#services:
|
10
|
+
# <SERVICE-NAME>.<CUSTOM-SERVICE-NAME>:
|
11
|
+
# depends_on:
|
12
|
+
# <SERVICE-NAME>.configure:
|
13
|
+
# condition: service_completed_successfully
|
14
|
+
# networks:
|
15
|
+
# app.ingress: {}
|
16
|
+
# profiles:
|
17
|
+
# - ${WORKFLOW_NAME}
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import os
|
2
|
-
|
2
|
+
import shutil
|
3
3
|
|
4
4
|
from .app import App
|
5
|
-
from .constants import DOTENV_FILE, NAMESERVERS_FILE
|
5
|
+
from .constants import DOTENV_PATH_ENV, DOTENV_FILE, NAMESERVERS_FILE
|
6
6
|
|
7
7
|
class WorkflowNamespace():
|
8
8
|
|
@@ -35,6 +35,12 @@ class WorkflowNamespace():
|
|
35
35
|
def traefik_config_path(self):
|
36
36
|
return os.path.join(self.path, 'traefik.yml')
|
37
37
|
|
38
|
+
def copy_dotenv(self):
|
39
|
+
dotenv_path = os.environ.get(DOTENV_PATH_ENV) or '.env'
|
40
|
+
|
41
|
+
if os.path.isfile(dotenv_path):
|
42
|
+
shutil.copy(dotenv_path, self.dotenv_path)
|
43
|
+
|
38
44
|
def traefik_config_relative_path(self, path: str):
|
39
45
|
if not path:
|
40
46
|
return path
|
@@ -10,7 +10,7 @@ from stoobly_agent.lib.logger import Logger
|
|
10
10
|
|
11
11
|
from .app import App
|
12
12
|
from .constants import (
|
13
|
-
APP_DIR_ENV, APP_NETWORK_ENV, CA_CERTS_DIR_ENV, CERTS_DIR_ENV, CONTEXT_DIR_ENV,
|
13
|
+
APP_DIR_ENV, APP_NETWORK_ENV, APP_PLUGINS_ENV, CA_CERTS_DIR_ENV, CERTS_DIR_ENV, CONTEXT_DIR_ENV,
|
14
14
|
SERVICE_DNS_ENV, SERVICE_NAME_ENV, SERVICE_SCRIPTS_DIR, SERVICE_SCRIPTS_ENV, USER_ID_ENV,
|
15
15
|
WORKFLOW_NAME_ENV, WORKFLOW_NAMESPACE_ENV, WORKFLOW_SCRIPTS_DIR, WORKFLOW_SCRIPTS_ENV, WORKFLOW_TEMPLATE_ENV
|
16
16
|
)
|
@@ -14,6 +14,7 @@ from stoobly_agent.app.cli.scaffold.app_create_command import AppCreateCommand
|
|
14
14
|
from stoobly_agent.app.cli.scaffold.constants import (
|
15
15
|
DOCKER_NAMESPACE, WORKFLOW_CONTAINER_PROXY, WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE
|
16
16
|
)
|
17
|
+
from stoobly_agent.app.cli.scaffold.constants import PLUGIN_CYPRESS, PLUGIN_PLAYWRIGHT
|
17
18
|
from stoobly_agent.app.cli.scaffold.containerized_app import ContainerizedApp
|
18
19
|
from stoobly_agent.app.cli.scaffold.docker.service.configure_gateway import configure_gateway
|
19
20
|
from stoobly_agent.app.cli.scaffold.docker.workflow.decorators_factory import get_workflow_decorators
|
@@ -89,7 +90,8 @@ def hostname(ctx):
|
|
89
90
|
help="Scaffold application"
|
90
91
|
)
|
91
92
|
@click.option('--app-dir-path', default=current_working_dir, help='Path to create the app scaffold.')
|
92
|
-
@click.option('--docker-socket-path', default='/var/run/docker.sock', help='Path to Docker socket.')
|
93
|
+
@click.option('--docker-socket-path', default='/var/run/docker.sock', type=click.Path(exists=True, file_okay=True, dir_okay=False), help='Path to Docker socket.')
|
94
|
+
@click.option('--plugin', multiple=True, type=click.Choice([PLUGIN_CYPRESS, PLUGIN_PLAYWRIGHT]), help='Scaffold integrations.')
|
93
95
|
@click.option('--quiet', is_flag=True, help='Disable log output.')
|
94
96
|
@click.option('--ui-port', default=4200, type=click.IntRange(1, 65535), help='UI service port.')
|
95
97
|
@click.argument('app_name', callback=validate_app_name)
|
@@ -101,7 +103,10 @@ def create(**kwargs):
|
|
101
103
|
if not kwargs['quiet'] and os.path.exists(app.scaffold_namespace_path):
|
102
104
|
print(f"{kwargs['app_dir_path']} already exists, updating scaffold maintained files...")
|
103
105
|
|
104
|
-
AppCreateCommand(app, **kwargs).build()
|
106
|
+
res = AppCreateCommand(app, **kwargs).build()
|
107
|
+
|
108
|
+
for warning in res['warnings']:
|
109
|
+
print(f"{bcolors.WARNING}WARNING{bcolors.ENDC}: {warning}")
|
105
110
|
|
106
111
|
@app.command(
|
107
112
|
help="Scaffold app service certs"
|
@@ -129,24 +134,20 @@ def mkcert(**kwargs):
|
|
129
134
|
@click.option('--detached', is_flag=True, help='Use isolated and non-persistent context directory.')
|
130
135
|
@click.option('--env', multiple=True, help='Specify an environment variable.')
|
131
136
|
@click.option('--hostname', callback=validate_hostname, help='Service hostname.')
|
137
|
+
@click.option('--local', is_flag=True, help='Specifies upstream service is local. Overrides `--upstream-hostname` option.')
|
132
138
|
@click.option('--port', type=click.IntRange(1, 65535), help='Service port.')
|
133
139
|
@click.option('--priority', default=5, type=click.FloatRange(1.0, 9.0), help='Determines the service run order. Lower values run first.')
|
134
|
-
@click.option('--proxy-mode', help=''
|
135
|
-
Proxy mode can be "regular", "transparent", "socks5",
|
136
|
-
"reverse:SPEC", or "upstream:SPEC". For reverse and
|
137
|
-
upstream proxy modes, SPEC is host specification in
|
138
|
-
the form of "http[s]://host[:port]".
|
139
|
-
''')
|
140
|
+
@click.option('--proxy-mode', type=click.Choice(['regular', 'reverse']), help='Proxy mode can be regular or reverse.')
|
140
141
|
@click.option('--quiet', is_flag=True, help='Disable log output.')
|
141
142
|
@click.option('--scheme', type=click.Choice(['http', 'https']), help='Defaults to https if hostname is set.')
|
143
|
+
@click.option('--upstream-hostname', callback=validate_hostname, help='Upstream service hostname.')
|
144
|
+
@click.option('--upstream-port', type=click.IntRange(1, 65535), help='Upstream service port.')
|
145
|
+
@click.option('--upstream-scheme', type=click.Choice(['http', 'https']), help='Upstream service scheme.')
|
142
146
|
@click.option('--workflow', multiple=True, type=click.Choice([WORKFLOW_MOCK_TYPE, WORKFLOW_RECORD_TYPE, WORKFLOW_TEST_TYPE]), help='Include pre-defined workflows.')
|
143
147
|
@click.argument('service_name', callback=validate_service_name)
|
144
148
|
def create(**kwargs):
|
145
149
|
__validate_app_dir(kwargs['app_dir_path'])
|
146
150
|
|
147
|
-
if kwargs.get("proxy_mode"):
|
148
|
-
__validate_proxy_mode(kwargs.get("proxy_mode"))
|
149
|
-
|
150
151
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
151
152
|
service = Service(kwargs['service_name'], app)
|
152
153
|
|
@@ -164,15 +165,17 @@ def create(**kwargs):
|
|
164
165
|
@click.option('--select', multiple=True, help='Select column(s) to display.')
|
165
166
|
@click.option('--service', multiple=True, help='Select specific services.')
|
166
167
|
@click.option('--without-headers', is_flag=True, default=False, help='Disable printing column headers.')
|
168
|
+
@click.option('--all', is_flag=True, default=False, help='Display all services including core and user defined services')
|
167
169
|
@click.option('--workflow', multiple=True, help='Specify workflow(s) to filter the services by. Defaults to all.')
|
168
170
|
def _list(**kwargs):
|
169
171
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
170
172
|
__validate_app(app)
|
171
173
|
|
172
|
-
|
174
|
+
without_core = not kwargs['all']
|
175
|
+
services = __get_services(app, service=kwargs['service'], without_core=without_core, workflow=kwargs['workflow'])
|
173
176
|
|
174
177
|
rows = []
|
175
|
-
for service_name in services:
|
178
|
+
for service_name in services:
|
176
179
|
service = Service(service_name, app)
|
177
180
|
__validate_service_dir(service.dir_path)
|
178
181
|
|
@@ -184,6 +187,22 @@ def _list(**kwargs):
|
|
184
187
|
|
185
188
|
print_services(rows, **select_print_options(kwargs))
|
186
189
|
|
190
|
+
@service.command(
|
191
|
+
help="Show information about a service",
|
192
|
+
)
|
193
|
+
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
194
|
+
@click.option('--format', type=click.Choice(FORMATS), help='Format output.')
|
195
|
+
@click.option('--without-headers', is_flag=True, default=False, help='Disable printing column headers.')
|
196
|
+
@click.argument('service_name')
|
197
|
+
@click.pass_context
|
198
|
+
def show(ctx, **kwargs):
|
199
|
+
service_name = kwargs['service_name']
|
200
|
+
del kwargs['service_name']
|
201
|
+
kwargs['service'] = [service_name]
|
202
|
+
|
203
|
+
# Invoke list with 1 service
|
204
|
+
ctx.invoke(_list, **kwargs)
|
205
|
+
|
187
206
|
@service.command(
|
188
207
|
help="Delete a service",
|
189
208
|
)
|
@@ -207,16 +226,15 @@ def delete(**kwargs):
|
|
207
226
|
)
|
208
227
|
@click.option('--app-dir-path', default=current_working_dir, help='Path to application directory.')
|
209
228
|
@click.option('--hostname', callback=validate_hostname, help='Service hostname.')
|
229
|
+
@click.option('--local', is_flag=True, help='Specifies upstream service is local. Overrides `--upstream-hostname` option.')
|
230
|
+
@click.option('--name', callback=validate_service_name, type=click.STRING, help='New name of the service to update to.')
|
210
231
|
@click.option('--port', type=click.IntRange(1, 65535), help='Service port.')
|
211
|
-
@click.option('--priority',
|
232
|
+
@click.option('--priority', type=click.FloatRange(1.0, 9.0), help='Determines the service run order. Lower values run first.')
|
233
|
+
@click.option('--proxy-mode', type=click.Choice(['regular', 'reverse']), help='Proxy mode can be regular or reverse.')
|
212
234
|
@click.option('--scheme', type=click.Choice(['http', 'https']), help='Defaults to https if hostname is set.')
|
213
|
-
@click.option('--
|
214
|
-
@click.option('--
|
215
|
-
|
216
|
-
"reverse:SPEC", or "upstream:SPEC". For reverse and
|
217
|
-
upstream proxy modes, SPEC is host specification in
|
218
|
-
the form of "http[s]://host[:port]".
|
219
|
-
''')
|
235
|
+
@click.option('--upstream-hostname', callback=validate_hostname, help='Upstream service hostname.')
|
236
|
+
@click.option('--upstream-port', type=click.IntRange(1, 65535), help='Upstream service port.')
|
237
|
+
@click.option('--upstream-scheme', type=click.Choice(['http', 'https']), help='Upstream service scheme.')
|
220
238
|
@click.argument('service_name')
|
221
239
|
def update(**kwargs):
|
222
240
|
app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
|
@@ -228,29 +246,32 @@ def update(**kwargs):
|
|
228
246
|
|
229
247
|
service_config = ServiceConfig(service.dir_path)
|
230
248
|
|
231
|
-
|
232
|
-
old_hostname = service_config.hostname
|
249
|
+
service_config.local = kwargs['local']
|
233
250
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
# If this is the default proxy_mode and the origin matches the original hostname, assume it is safe to update with the new hostname
|
238
|
-
if service_config.proxy_mode.startswith("reverse:"):
|
239
|
-
old_origin = service_config.proxy_mode.split("reverse:")[1]
|
240
|
-
parsed_origin_url = urlparse(old_origin)
|
251
|
+
if kwargs['hostname']:
|
252
|
+
service_config.hostname = kwargs['hostname']
|
241
253
|
|
242
|
-
|
243
|
-
|
254
|
+
if kwargs['port']:
|
255
|
+
service_config.port = kwargs['port']
|
244
256
|
|
245
257
|
if kwargs['priority']:
|
246
258
|
service_config.priority = kwargs['priority']
|
247
259
|
|
248
|
-
if kwargs['
|
249
|
-
service_config.
|
260
|
+
if kwargs['proxy_mode']:
|
261
|
+
service_config.proxy_mode = kwargs['proxy_mode']
|
250
262
|
|
251
263
|
if kwargs['scheme']:
|
252
264
|
service_config.scheme = kwargs['scheme']
|
253
265
|
|
266
|
+
if kwargs['upstream_hostname']:
|
267
|
+
service_config.upstream_hostname = kwargs['upstream_hostname']
|
268
|
+
|
269
|
+
if kwargs['upstream_port']:
|
270
|
+
service_config.upstream_port = kwargs['upstream_port']
|
271
|
+
|
272
|
+
if kwargs['upstream_scheme']:
|
273
|
+
service_config.upstream_scheme = kwargs['upstream_scheme']
|
274
|
+
|
254
275
|
if kwargs['name']:
|
255
276
|
old_service_name = service.service_name
|
256
277
|
new_service_name = kwargs['name']
|
@@ -264,10 +285,6 @@ def update(**kwargs):
|
|
264
285
|
|
265
286
|
print(f"Successfully renamed service to: {new_service_name}")
|
266
287
|
|
267
|
-
if kwargs['proxy_mode']:
|
268
|
-
__validate_proxy_mode(kwargs['proxy_mode'])
|
269
|
-
service_config.proxy_mode = kwargs['proxy_mode']
|
270
|
-
|
271
288
|
service_config.write()
|
272
289
|
|
273
290
|
@workflow.command(
|
@@ -314,7 +331,7 @@ def copy(**kwargs):
|
|
314
331
|
config = { **kwargs }
|
315
332
|
del config['service']
|
316
333
|
config['service_name'] = service_name
|
317
|
-
|
334
|
+
|
318
335
|
command = WorkflowCopyCommand(app, **config)
|
319
336
|
|
320
337
|
if not command.app_dir_exists:
|
@@ -339,7 +356,7 @@ def copy(**kwargs):
|
|
339
356
|
@click.option('--service', multiple=True, help='Select which services to log. Defaults to all.')
|
340
357
|
@click.option('--user-id', default=os.getuid(), help='OS user ID of the owner of context dir path.')
|
341
358
|
@click.argument('workflow_name')
|
342
|
-
def down(**kwargs):
|
359
|
+
def down(**kwargs):
|
343
360
|
os.environ[env_vars.LOG_LEVEL] = kwargs['log_level']
|
344
361
|
|
345
362
|
containerized = kwargs['containerized']
|
@@ -349,6 +366,7 @@ def down(**kwargs):
|
|
349
366
|
__validate_app(app)
|
350
367
|
|
351
368
|
__with_namespace_defaults(kwargs)
|
369
|
+
__with_workflow_namespace(app, kwargs['namespace'])
|
352
370
|
|
353
371
|
services = __get_services(
|
354
372
|
app, service=kwargs['service'], workflow=[kwargs['workflow_name']]
|
@@ -383,7 +401,7 @@ def down(**kwargs):
|
|
383
401
|
)
|
384
402
|
if not exec_command:
|
385
403
|
continue
|
386
|
-
|
404
|
+
|
387
405
|
print(exec_command, file=script)
|
388
406
|
|
389
407
|
# After services are stopped, their network needs to be removed
|
@@ -447,7 +465,7 @@ def logs(**kwargs):
|
|
447
465
|
continue
|
448
466
|
|
449
467
|
config = { **kwargs }
|
450
|
-
config['service_name'] = service
|
468
|
+
config['service_name'] = service
|
451
469
|
command = WorkflowLogCommand(app, **config)
|
452
470
|
commands.append(command)
|
453
471
|
|
@@ -503,6 +521,7 @@ def up(**kwargs):
|
|
503
521
|
__validate_app(app)
|
504
522
|
|
505
523
|
__with_namespace_defaults(kwargs)
|
524
|
+
workflow_namespace = __with_workflow_namespace(app, kwargs['namespace'])
|
506
525
|
|
507
526
|
services = __get_services(
|
508
527
|
app, service=kwargs['service'], workflow=[kwargs['workflow_name']]
|
@@ -514,11 +533,7 @@ def up(**kwargs):
|
|
514
533
|
|
515
534
|
# Gateway ports are dynamically set depending on the workflow run
|
516
535
|
workflow = Workflow(kwargs['workflow_name'], app)
|
517
|
-
configure_gateway(
|
518
|
-
WorkflowNamespace(app, kwargs['namespace'] or workflow.workflow_name),
|
519
|
-
workflow.service_paths_from_services(services),
|
520
|
-
kwargs['no_publish']
|
521
|
-
)
|
536
|
+
configure_gateway(workflow_namespace, workflow.service_paths_from_services(services), kwargs['no_publish'])
|
522
537
|
|
523
538
|
commands: List[WorkflowRunCommand] = []
|
524
539
|
for service in services:
|
@@ -587,7 +602,7 @@ def validate(**kwargs):
|
|
587
602
|
__validate_app(app)
|
588
603
|
|
589
604
|
workflow = Workflow(kwargs['workflow_name'], app)
|
590
|
-
|
605
|
+
|
591
606
|
config = { **kwargs }
|
592
607
|
config['service_name'] = 'build'
|
593
608
|
|
@@ -627,7 +642,7 @@ def install(**kwargs):
|
|
627
642
|
)
|
628
643
|
|
629
644
|
hostnames = []
|
630
|
-
for service_name in services:
|
645
|
+
for service_name in services:
|
631
646
|
service = Service(service_name, app)
|
632
647
|
__validate_service_dir(service.dir_path)
|
633
648
|
|
@@ -659,7 +674,7 @@ def uninstall(**kwargs):
|
|
659
674
|
)
|
660
675
|
|
661
676
|
hostnames = []
|
662
|
-
for service_name in services:
|
677
|
+
for service_name in services:
|
663
678
|
service = Service(service_name, app)
|
664
679
|
__validate_service_dir(service.dir_path)
|
665
680
|
|
@@ -736,7 +751,7 @@ def __get_services(app: App, **kwargs):
|
|
736
751
|
|
737
752
|
services = list(set(selected_services))
|
738
753
|
services.sort()
|
739
|
-
|
754
|
+
|
740
755
|
return services
|
741
756
|
|
742
757
|
def __print_header(text: str):
|
@@ -773,7 +788,7 @@ def __services_mkcert(app: App, services):
|
|
773
788
|
continue
|
774
789
|
|
775
790
|
hostname = service_config.hostname
|
776
|
-
|
791
|
+
|
777
792
|
if not hostname:
|
778
793
|
continue
|
779
794
|
|
@@ -788,8 +803,11 @@ def __validate_app(app: App):
|
|
788
803
|
sys.exit(1)
|
789
804
|
|
790
805
|
def __validate_app_dir(app_dir_path):
|
791
|
-
|
792
|
-
|
806
|
+
__validate_dir(app_dir_path)
|
807
|
+
|
808
|
+
def __validate_dir(dir_path):
|
809
|
+
if not os.path.exists(dir_path):
|
810
|
+
print(f"Error: {dir_path} does not exist", file=sys.stderr)
|
793
811
|
sys.exit(1)
|
794
812
|
|
795
813
|
def __validate_service_dir(service_dir_path):
|
@@ -797,35 +815,6 @@ def __validate_service_dir(service_dir_path):
|
|
797
815
|
print(f"Error: '{service_dir_path}' does not exist, please scaffold this service", file=sys.stderr)
|
798
816
|
sys.exit(1)
|
799
817
|
|
800
|
-
def __validate_proxy_mode(proxy_mode: str) -> None:
|
801
|
-
valid_exact_matches = {
|
802
|
-
"regular": None,
|
803
|
-
"transparent": None,
|
804
|
-
"socks5": None,
|
805
|
-
}
|
806
|
-
|
807
|
-
valid_prefixes = {
|
808
|
-
"reverse": None,
|
809
|
-
"upstream": None
|
810
|
-
}
|
811
|
-
|
812
|
-
if proxy_mode in valid_exact_matches:
|
813
|
-
return
|
814
|
-
|
815
|
-
split_str = proxy_mode.split(":", 1)
|
816
|
-
if len(split_str) != 2:
|
817
|
-
print(f"Error: {proxy_mode} is invalid.", file=sys.stderr)
|
818
|
-
sys.exit(1)
|
819
|
-
|
820
|
-
prefix = split_str[0]
|
821
|
-
spec = split_str[1]
|
822
|
-
|
823
|
-
if prefix not in valid_prefixes:
|
824
|
-
print(f"Error: {proxy_mode} is invalid.", file=sys.stderr)
|
825
|
-
sys.exit(1)
|
826
|
-
|
827
|
-
# TODO: validate SPEC
|
828
|
-
|
829
818
|
def __with_namespace_defaults(kwargs):
|
830
819
|
if not kwargs.get('namespace'):
|
831
820
|
kwargs['namespace'] = kwargs.get('workflow_name')
|
@@ -840,3 +829,8 @@ def __workflow_create(app, **kwargs):
|
|
840
829
|
template=kwargs['template'],
|
841
830
|
workflow_decorators=workflow_decorators
|
842
831
|
)
|
832
|
+
|
833
|
+
def __with_workflow_namespace(app: App, namespace: str):
|
834
|
+
workflow_namespace = WorkflowNamespace(app, namespace)
|
835
|
+
workflow_namespace.copy_dotenv()
|
836
|
+
return workflow_namespace
|