Skip to content
Open
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
9 changes: 0 additions & 9 deletions .bumpversion.cfg

This file was deleted.

9 changes: 6 additions & 3 deletions .github/workflows/pypi-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
branches:
- main

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
build-n-publish:
name: Build and publish Python
Expand All @@ -13,13 +16,13 @@ jobs:
max-parallel: 1
matrix:
python-version:
- 3.8
- "3.8"
os:
- ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v1
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install tox
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/pypi-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
branches-ignore:
- main

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
build-n-publish:
name: Build and publish Python
Expand All @@ -13,13 +16,13 @@ jobs:
max-parallel: 1
matrix:
python-version:
- 3.8
- "3.8"
os:
- ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v1
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install tox
Expand Down
15 changes: 12 additions & 3 deletions .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
build:
name: Execute Unit Tests
Expand All @@ -16,15 +19,21 @@ jobs:
max-parallel: 1
matrix:
python-version:
- 3.8
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
os:
- ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v1
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install modules
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ ipython_config.py
# install all needed dependencies.
#Pipfile.lock

# uv
uv.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

Expand Down Expand Up @@ -134,4 +137,5 @@ dmypy.json
gremlin-python.iml

_*.json
_t.py
_t.py
ENDPOINTS.md
20 changes: 5 additions & 15 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,11 @@

Package version control conforms to semantic versioning.

This package uses [bumpversion](https://github.com/peritus/bumpversion) to manage the version
The version is defined statically in `pyproject.toml`:

Quick usage:

Major version update:
```bash
bumpversion --new-version 1.0.0 major
```

Minor version update:
```bash
bumpversion --new-version 0.4.0 minor
```toml
[project]
version = "0.19.3"
```

Patch version update:
```bash
bumpversion --new-version 0.3.13 patch
```
To cut a release, update the `version` field manually and open a PR against `main`.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
prune tools
50 changes: 0 additions & 50 deletions Makefile

This file was deleted.

13 changes: 1 addition & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,11 @@ pip3 install gremlinapi
```bash
git clone git@github.com:gremlin/gremlin-python.git
cd gremlin-python
python3 setup.py install
```

### Use Packaged Docker runtime

Build and run this project's self contained docker image with all the necessary dependencies
```shell script
make docker-build && make docker-run-interactive
pip3 install .
```

## Usage

### CLI

Coming soon

## Authenticate to the API

The Gremlin API requires a form of authentication, either API Key or Bearer Token. API Keys are the least privileged
Expand Down
19 changes: 11 additions & 8 deletions gremlinapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,24 @@
class SecretsFilter(logging.Filter):
def filter(self, record):
secret_length = 5
if len(GremlinAPIConfig.api_key) >= secret_length:
api_key = GremlinAPIConfig.api_key or ""
bearer_token = GremlinAPIConfig.bearer_token or ""
password = GremlinAPIConfig.password or ""
if len(api_key) >= secret_length:
record.msg = re.sub(
rf"{GremlinAPIConfig.api_key}[\'\s]?",
"..." + GremlinAPIConfig.api_key[-4:],
rf"{re.escape(api_key)}[\'\s]?",
"..." + api_key[-4:],
record.msg,
)
if len(GremlinAPIConfig.bearer_token) >= secret_length:
if len(bearer_token) >= secret_length:
record.msg = re.sub(
rf"{GremlinAPIConfig.bearer_token}[\'\s]?",
"..." + GremlinAPIConfig.bearer_token[-4:],
rf"{re.escape(bearer_token)}[\'\s]?",
"..." + bearer_token[-4:],
record.msg,
)
if len(GremlinAPIConfig.password) >= secret_length:
if len(password) >= secret_length:
record.msg = re.sub(
rf"{GremlinAPIConfig.password}[\'\s]?",
rf"{re.escape(password)}[\'\s]?",
"[PASSWORD REDACTED]",
record.msg,
)
Expand Down
55 changes: 40 additions & 15 deletions gremlinapi/attacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,27 @@ def list_active_attacks(
https_client: Type[GremlinAPIHttpClient] = get_gremlin_httpclient(),
*args: tuple,
**kwargs: dict,
) -> dict:
) -> list:
method: str = "GET"
endpoint: str = cls._list_endpoint("/attacks/active", **kwargs)
payload: dict = cls._payload(**{"headers": https_client.header()})
(resp, body) = https_client.api_call(method, endpoint, **payload)
return body
source: str = kwargs.get("source", None)
page_size = kwargs.get("pageSize", None)
page_token: str = None
all_items: list = []
while True:
endpoint = cls._optional_team_endpoint("/attacks/active/paged", **kwargs)
if source and source.lower() in ("adhoc", "scenario"):
endpoint = cls._add_query_param(endpoint, "source", source)
if page_size:
endpoint = cls._add_query_param(endpoint, "pageSize", str(page_size))
if page_token:
endpoint = cls._add_query_param(endpoint, "pageToken", page_token)
payload: dict = cls._payload(**{"headers": https_client.header()})
(resp, body) = https_client.api_call(method, endpoint, **payload)
all_items.extend(body.get("items", []))
page_token = body.get("pageToken") or None
if not page_token:
Comment thread
sewhyte marked this conversation as resolved.
break
return all_items

@classmethod
@register_cli_action("list_attacks", ("",), ("source", "pageSize", "teamId"))
Expand Down Expand Up @@ -110,17 +125,27 @@ def list_completed_attacks(
https_client: Type[GremlinAPIHttpClient] = get_gremlin_httpclient(),
*args: tuple,
**kwargs: dict,
) -> dict:
"""
:param https_client:
:param kwargs: { source(adhoc or scenario, query), pageSize(int32, query), teamId(string, query) }
:return:
"""
) -> list:
method: str = "GET"
endpoint: str = cls._list_endpoint("/attacks/completed", **kwargs)
payload: dict = cls._payload(**{"headers": https_client.header()})
(resp, body) = https_client.api_call(method, endpoint, **payload)
return body
source: str = kwargs.get("source", None)
page_size = kwargs.get("pageSize", None)
page_token: str = None
all_items: list = []
while True:
endpoint = cls._optional_team_endpoint("/attacks/completed/paged", **kwargs)
if source and source.lower() in ("adhoc", "scenario"):
endpoint = cls._add_query_param(endpoint, "source", source)
if page_size:
endpoint = cls._add_query_param(endpoint, "pageSize", str(page_size))
if page_token:
endpoint = cls._add_query_param(endpoint, "pageToken", page_token)
payload: dict = cls._payload(**{"headers": https_client.header()})
(resp, body) = https_client.api_call(method, endpoint, **payload)
all_items.extend(body.get("items", []))
page_token = body.get("pageToken") or None
if not page_token:
Comment on lines +145 to +146
break
return all_items

@classmethod
@register_cli_action("get_attack", ("guid",), ("teamId",))
Expand Down
24 changes: 18 additions & 6 deletions gremlinapi/companies.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,18 +157,30 @@ def update_company_saml_props(
return body

@classmethod
@register_cli_action("list_company_users", ("identifier",), ("",))
@register_cli_action("list_company_users", ("identifier",), ("pageSize",))
def list_company_users(
cls,
https_client: Type[GremlinAPIHttpClient] = get_gremlin_httpclient(),
**kwargs: dict,
) -> dict:
) -> list:
method: str = "GET"
identifier: str = cls._error_if_not_param("identifier", **kwargs)
endpoint: str = f"/companies/{identifier}/users"
payload: dict = cls._payload(**{"headers": https_client.header()})
(resp, body) = https_client.api_call(method, endpoint, **payload)
return body
page_size = kwargs.get("pageSize", None)
page_token: str = None
all_items: list = []
while True:
endpoint: str = f"/companies/{identifier}/users/paged"
if page_size:
endpoint = cls._add_query_param(endpoint, "pageSize", str(page_size))
if page_token:
endpoint = cls._add_query_param(endpoint, "pageToken", page_token)
payload: dict = cls._payload(**{"headers": https_client.header()})
(resp, body) = https_client.api_call(method, endpoint, **payload)
all_items.extend(body.get("items", []))
page_token = body.get("page_token") or None
Comment thread
sewhyte marked this conversation as resolved.
if not page_token:
break
return all_items

@classmethod
@register_cli_action(
Expand Down
Loading
Loading