diff --git a/CLAUDE.md b/CLAUDE.md
index b0217e6..29db243 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -212,6 +212,8 @@ Commits use **conventional commit** prefixes which affect both versioning and ch
Best practice: Use `feat:` for new features (will bump minor) and `fix:` for bug fixes (will bump patch). These prefixes satisfy both the version bumping and changelog generation.
+**Important**: `feat:` and `fix:` must only be used for commits that affect the CLI deliverable (i.e., changes under `cortexapps_cli/`, `pyproject.toml`, `poetry.lock`, `tests/`, `docker/`). Commits that only touch non-deliverable paths (`internal/`, `docs/superpowers/`, Justfile recipes for internal tooling, etc.) must use `chore:` or `docs:` prefixes to avoid unintended version bumps.
+
### HISTORY.md Merge Conflicts
The `HISTORY.md` file is auto-generated when `staging` is merged to `main`. This means:
- `main` always has the latest HISTORY.md
diff --git a/HISTORY.md b/HISTORY.md
index ea61845..1781d17 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -6,6 +6,33 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+## [1.16.0](https://github.com/cortexapps/cli/releases/tag/1.16.0) - 2026-05-14
+
+[Compare with 1.15.0](https://github.com/cortexapps/cli/compare/1.15.0...1.16.0)
+
+### Features
+
+- shared minikube cluster, env prompting, cortex deploy recipes ([ff7c67d](https://github.com/cortexapps/cli/commit/ff7c67db11b47c32be7018b1f8660d9e7e148ed7) by Jeff Schnitter).
+- add Cortex Helm values and env vars for minikube deploy ([23a8994](https://github.com/cortexapps/cli/commit/23a89943e20164c73630fdaee0071cd88cc553f9) by Jeff Schnitter).
+- consolidate internal tooling — axon, k8s, prometheus, ui, functional tests ([1fd70fe](https://github.com/cortexapps/cli/commit/1fd70fe5b212516704e91e0022c10e6c6c3aed3c) by Jeff Schnitter).
+- add ske-setup, ske-test, and ske-stop Justfile recipes ([6f56dca](https://github.com/cortexapps/cli/commit/6f56dca1afcd48ebc23fe3cf5a70bc2760b60257) by Jeff Schnitter).
+- add SKE Helm values and ArgoCD Application manifest ([3d83ca5](https://github.com/cortexapps/cli/commit/3d83ca567861c64cea08d2dedee545fee0aff278) by Jeff Schnitter).
+- add ConfigMap Promise for SKE Cortex integration test ([7942967](https://github.com/cortexapps/cli/commit/7942967b83eb121fbd2ec606a14dfd86c5a73036) by Jeff Schnitter).
+- add SKE directory structure and env config ([dc0cd54](https://github.com/cortexapps/cli/commit/dc0cd54e6126bc4e5f3498db85f7cf862ea261c2) by Jeff Schnitter).
+
+### Bug Fixes
+
+- add __init__.py to trigger publish workflow for urllib3 update ([854dcb0](https://github.com/cortexapps/cli/commit/854dcb00b534df21b6a1976c7a4e79fca613f246) by Jeff Schnitter).
+- update urllib3 to 2.7.0 to resolve high-severity CVEs ([904a144](https://github.com/cortexapps/cli/commit/904a144e02e65067f2029d85baa5efdf83c535f6) by Jeff Schnitter).
+- prompt for env vars before starting minikube ([3da7a87](https://github.com/cortexapps/cli/commit/3da7a870853591f48f0eb3e80013e0d6573f9f62) by Jeff Schnitter).
+- review fixes — quote .env values, fix pod selector, remove dead PID files ([4931dae](https://github.com/cortexapps/cli/commit/4931daed4e8a10ab6b9842d6aa70e6b4ce81c94b) by Jeff Schnitter).
+- use external script for env prompting to fix just quoting bug ([5816d65](https://github.com/cortexapps/cli/commit/5816d65c7ca55834b7eb0a1e819b5192d2463853) by Jeff Schnitter).
+- use correct Cortex API endpoint for Entity Type check in ske-test ([94f774e](https://github.com/cortexapps/cli/commit/94f774e43d7849175224794cfd8c8d7744b6c925) by Jeff Schnitter).
+- wait for cert-manager webhook CA, let SKE auto-create Destination ([a4983fe](https://github.com/cortexapps/cli/commit/a4983fe75946c37e2eb582098a21754a10c498c8) by Jeff Schnitter).
+- update SKE recipes with GitStateStore, Destination, and git-based resource requests ([8dad6be](https://github.com/cortexapps/cli/commit/8dad6bea12ff52011a2cbe1f15898d2eb91f9ad7) by Jeff Schnitter).
+- correct SKE setup — add cert-manager, use latest version, fix integrationAlias ([4e1d2d7](https://github.com/cortexapps/cli/commit/4e1d2d74a096da9d6ff28e4cbedb7c6c50b4d8d6) by Jeff Schnitter).
+- indent inline Python in ske-test recipe for just compatibility ([8a57c3c](https://github.com/cortexapps/cli/commit/8a57c3c21a10795baa1a59ca290af14f018ff813) by Jeff Schnitter).
+
## [1.15.0](https://github.com/cortexapps/cli/releases/tag/1.15.0) - 2026-04-15
[Compare with 1.14.0](https://github.com/cortexapps/cli/compare/1.14.0...1.15.0)
diff --git a/cortexapps_cli/__init__.py b/cortexapps_cli/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/cortexapps_cli/commands/integrations.py b/cortexapps_cli/commands/integrations.py
index 5e2f7c7..0586928 100644
--- a/cortexapps_cli/commands/integrations.py
+++ b/cortexapps_cli/commands/integrations.py
@@ -3,33 +3,99 @@
import typer
from typing_extensions import Annotated
+import cortexapps_cli.commands.integrations_commands.apiiro as apiiro
+import cortexapps_cli.commands.integrations_commands.argocd as argocd
import cortexapps_cli.commands.integrations_commands.aws as aws
+import cortexapps_cli.commands.integrations_commands.azure_active_directory as azure_active_directory
import cortexapps_cli.commands.integrations_commands.azure_resources as azure_resources
import cortexapps_cli.commands.integrations_commands.azure_devops as azure_devops
+import cortexapps_cli.commands.integrations_commands.bamboohr as bamboohr
+import cortexapps_cli.commands.integrations_commands.bitbucket as bitbucket
+import cortexapps_cli.commands.integrations_commands.bugsnag as bugsnag
+import cortexapps_cli.commands.integrations_commands.buildkite as buildkite
+import cortexapps_cli.commands.integrations_commands.checkmarx_sast as checkmarx_sast
import cortexapps_cli.commands.integrations_commands.circleci as circleci
+import cortexapps_cli.commands.integrations_commands.clickup as clickup
+import cortexapps_cli.commands.integrations_commands.codecov as codecov
import cortexapps_cli.commands.integrations_commands.coralogix as coralogix
import cortexapps_cli.commands.integrations_commands.datadog as datadog
+import cortexapps_cli.commands.integrations_commands.dynatrace as dynatrace
+import cortexapps_cli.commands.integrations_commands.firehydrant as firehydrant
import cortexapps_cli.commands.integrations_commands.github as github
import cortexapps_cli.commands.integrations_commands.gitlab as gitlab
import cortexapps_cli.commands.integrations_commands.incidentio as incidentio
+import cortexapps_cli.commands.integrations_commands.instana as instana
+import cortexapps_cli.commands.integrations_commands.jenkins as jenkins
+import cortexapps_cli.commands.integrations_commands.jira as jira
import cortexapps_cli.commands.integrations_commands.launchdarkly as launchdarkly
+import cortexapps_cli.commands.integrations_commands.mend_sast as mend_sast
+import cortexapps_cli.commands.integrations_commands.mend_sca as mend_sca
import cortexapps_cli.commands.integrations_commands.newrelic as newrelic
+import cortexapps_cli.commands.integrations_commands.okta as okta
+import cortexapps_cli.commands.integrations_commands.opsgenie as opsgenie
import cortexapps_cli.commands.integrations_commands.pagerduty as pagerduty
import cortexapps_cli.commands.integrations_commands.prometheus as prometheus
+import cortexapps_cli.commands.integrations_commands.rollbar as rollbar
+import cortexapps_cli.commands.integrations_commands.rootly as rootly
+import cortexapps_cli.commands.integrations_commands.semgrep as semgrep
+import cortexapps_cli.commands.integrations_commands.sentry as sentry
+import cortexapps_cli.commands.integrations_commands.servicenow as servicenow
+import cortexapps_cli.commands.integrations_commands.servicenow_cloud_observability as servicenow_cloud_observability
+import cortexapps_cli.commands.integrations_commands.snyk as snyk
import cortexapps_cli.commands.integrations_commands.sonarqube as sonarqube
+import cortexapps_cli.commands.integrations_commands.splunk_observability_cloud as splunk_observability_cloud
+import cortexapps_cli.commands.integrations_commands.splunk_on_call as splunk_on_call
+import cortexapps_cli.commands.integrations_commands.sumologic as sumologic
+import cortexapps_cli.commands.integrations_commands.veracode as veracode
+import cortexapps_cli.commands.integrations_commands.wiz as wiz
+import cortexapps_cli.commands.integrations_commands.workday as workday
+import cortexapps_cli.commands.integrations_commands.xmatters as xmatters
app = typer.Typer(help="Integrations commands", no_args_is_help=True)
+app.add_typer(apiiro.app, name="apiiro")
+app.add_typer(argocd.app, name="argocd")
app.add_typer(aws.app, name="aws")
+app.add_typer(azure_active_directory.app, name="azure-active-directory")
app.add_typer(azure_resources.app, name="azure-resources")
app.add_typer(azure_devops.app, name="azure-devops")
+app.add_typer(bamboohr.app, name="bamboohr")
+app.add_typer(bitbucket.app, name="bitbucket")
+app.add_typer(bugsnag.app, name="bugsnag")
+app.add_typer(buildkite.app, name="buildkite")
+app.add_typer(checkmarx_sast.app, name="checkmarx-sast")
app.add_typer(circleci.app, name="circleci")
+app.add_typer(clickup.app, name="clickup")
+app.add_typer(codecov.app, name="codecov")
app.add_typer(coralogix.app, name="coralogix")
app.add_typer(datadog.app, name="datadog")
+app.add_typer(dynatrace.app, name="dynatrace")
+app.add_typer(firehydrant.app, name="firehydrant")
app.add_typer(github.app, name="github")
app.add_typer(gitlab.app, name="gitlab")
app.add_typer(incidentio.app, name="incidentio")
+app.add_typer(instana.app, name="instana")
+app.add_typer(jenkins.app, name="jenkins")
+app.add_typer(jira.app, name="jira")
app.add_typer(launchdarkly.app, name="launchdarkly")
+app.add_typer(mend_sast.app, name="mend-sast")
+app.add_typer(mend_sca.app, name="mend-sca")
app.add_typer(newrelic.app, name="newrelic")
+app.add_typer(okta.app, name="okta")
+app.add_typer(opsgenie.app, name="opsgenie")
app.add_typer(pagerduty.app, name="pagerduty")
app.add_typer(prometheus.app, name="prometheus")
+app.add_typer(rollbar.app, name="rollbar")
+app.add_typer(rootly.app, name="rootly")
+app.add_typer(semgrep.app, name="semgrep")
+app.add_typer(sentry.app, name="sentry")
+app.add_typer(servicenow.app, name="servicenow")
+app.add_typer(servicenow_cloud_observability.app, name="servicenow-cloud-observability")
+app.add_typer(snyk.app, name="snyk")
app.add_typer(sonarqube.app, name="sonarqube")
+app.add_typer(splunk_observability_cloud.app, name="splunk-observability-cloud")
+app.add_typer(splunk_on_call.app, name="splunk-on-call")
+app.add_typer(sumologic.app, name="sumo-logic")
+app.add_typer(veracode.app, name="veracode")
+app.add_typer(wiz.app, name="wiz")
+app.add_typer(workday.app, name="workday")
+app.add_typer(xmatters.app, name="xmatters")
diff --git a/cortexapps_cli/commands/integrations_commands/apiiro.py b/cortexapps_cli/commands/integrations_commands/apiiro.py
new file mode 100644
index 0000000..48b0207
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/apiiro.py
@@ -0,0 +1,167 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Apiiro commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="Alias for this configuration"),
+ api_key: str = typer.Option(..., "--api-key", "-api", help="API key"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a single configuration
+ """
+
+ client = ctx.obj["client"]
+
+ if file_input:
+ if alias or api_key or is_default:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "alias": alias,
+ "apiKey": api_key,
+ "isDefault": is_default,
+ }
+
+ # remove any data elements that are None
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/apiiro/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def add_multiple(
+ ctx: typer.Context,
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add multiple configurations
+ """
+
+ client = ctx.obj["client"]
+
+ data = json.loads("".join([line for line in file_input]))
+
+ r = client.put("api/v1/apiiro/configurations", data=data)
+ print_json(data=r)
+
+@app.command()
+def delete(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Delete a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/apiiro/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def delete_all(
+ ctx: typer.Context,
+):
+ """
+ Delete all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/apiiro/configurations")
+ print_json(data=r)
+
+@app.command()
+def get(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Get a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/apiiro/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def list(
+ ctx: typer.Context,
+):
+ """
+ Get all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/apiiro/configurations")
+ print_json(data=r)
+
+@app.command()
+def get_default(
+ ctx: typer.Context,
+):
+ """
+ Get default configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/apiiro/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+):
+ """
+ Update a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ data = {
+ "alias": alias,
+ "isDefault": is_default
+ }
+
+ r = client.put("api/v1/apiiro/configuration/" + alias, data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Validate a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/apiiro/configuration/validate/" + alias)
+ print_json(data=r)
+
+@app.command()
+def validate_all(
+ ctx: typer.Context,
+):
+ """
+ Validate all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/apiiro/configuration/validate")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/argocd.py b/cortexapps_cli/commands/integrations_commands/argocd.py
new file mode 100644
index 0000000..58801dc
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/argocd.py
@@ -0,0 +1,171 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="ArgoCD commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="Alias for this configuration"),
+ host: str = typer.Option(..., "--host", "-h", help="Host name"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+ password: str = typer.Option(..., "--password", "-p", help="Password"),
+ username: str = typer.Option(..., "--username", "-u", help="Username"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a single configuration
+ """
+
+ client = ctx.obj["client"]
+
+ if file_input:
+ if alias or host or is_default or password or username:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "alias": alias,
+ "host": host,
+ "isDefault": is_default,
+ "password": password,
+ "username": username,
+ }
+
+ # remove any data elements that are None
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/argocd/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def add_multiple(
+ ctx: typer.Context,
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add multiple configurations
+ """
+
+ client = ctx.obj["client"]
+
+ data = json.loads("".join([line for line in file_input]))
+
+ r = client.put("api/v1/argocd/configurations", data=data)
+ print_json(data=r)
+
+@app.command()
+def delete(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Delete a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/argocd/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def delete_all(
+ ctx: typer.Context,
+):
+ """
+ Delete all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/argocd/configurations")
+ print_json(data=r)
+
+@app.command()
+def get(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Get a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/argocd/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def list(
+ ctx: typer.Context,
+):
+ """
+ Get all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/argocd/configurations")
+ print_json(data=r)
+
+@app.command()
+def get_default(
+ ctx: typer.Context,
+):
+ """
+ Get default configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/argocd/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+):
+ """
+ Update a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ data = {
+ "alias": alias,
+ "isDefault": is_default
+ }
+
+ r = client.put("api/v1/argocd/configuration/" + alias, data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Validate a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/argocd/configuration/validate/" + alias)
+ print_json(data=r)
+
+@app.command()
+def validate_all(
+ ctx: typer.Context,
+):
+ """
+ Validate all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/argocd/configuration/validate")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/azure_active_directory.py b/cortexapps_cli/commands/integrations_commands/azure_active_directory.py
new file mode 100644
index 0000000..1003912
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/azure_active_directory.py
@@ -0,0 +1,89 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Azure Active Directory commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ client_id: str = typer.Option(None, "--client-id", "-ci", help="Client ID"),
+ client_secret: str = typer.Option(None, "--client-secret", "-cs", help="Client secret"),
+ tenant_id: str = typer.Option(None, "--tenant-id", "-ti", help="Tenant ID"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if client_id or client_secret or tenant_id:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "clientId": client_id,
+ "clientSecret": client_secret,
+ "tenantId": tenant_id,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/active-directory/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/active-directory/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ client_id: str = typer.Option(None, "--client-id", "-ci", help="Client ID"),
+ client_secret: str = typer.Option(None, "--client-secret", "-cs", help="Client secret"),
+ tenant_id: str = typer.Option(None, "--tenant-id", "-ti", help="Tenant ID"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if client_id or client_secret or tenant_id:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "clientId": client_id,
+ "clientSecret": client_secret,
+ "tenantId": tenant_id,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/active-directory/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/active-directory/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/active-directory/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/bamboohr.py b/cortexapps_cli/commands/integrations_commands/bamboohr.py
new file mode 100644
index 0000000..0e4e43a
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/bamboohr.py
@@ -0,0 +1,85 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="BambooHR commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ subdomain: str = typer.Option(None, "--subdomain", "-s", help="Subdomain"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token or subdomain:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ "subdomain": subdomain,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/bamboohr/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/bamboohr/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ subdomain: str = typer.Option(None, "--subdomain", "-s", help="Subdomain"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token or subdomain:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ "subdomain": subdomain,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/bamboohr/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/bamboohr/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/bamboohr/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/bitbucket.py b/cortexapps_cli/commands/integrations_commands/bitbucket.py
new file mode 100644
index 0000000..bf8e619
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/bitbucket.py
@@ -0,0 +1,152 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Bitbucket commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a single configuration (use --file for JSON input)
+ """
+
+ client = ctx.obj["client"]
+
+ data = json.loads("".join([line for line in file_input]))
+
+ r = client.post("api/v1/bitbucket/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def add_multiple(
+ ctx: typer.Context,
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add multiple configurations
+ """
+
+ client = ctx.obj["client"]
+
+ data = json.loads("".join([line for line in file_input]))
+
+ r = client.put("api/v1/bitbucket/configurations", data=data)
+ print_json(data=r)
+
+@app.command()
+def delete(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Delete a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/bitbucket/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def delete_all(
+ ctx: typer.Context,
+):
+ """
+ Delete all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/bitbucket/configurations")
+ print_json(data=r)
+
+@app.command()
+def get(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Get a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/bitbucket/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def list(
+ ctx: typer.Context,
+):
+ """
+ Get all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/bitbucket/configurations")
+ print_json(data=r)
+
+@app.command()
+def get_default(
+ ctx: typer.Context,
+):
+ """
+ Get default configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/bitbucket/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+):
+ """
+ Update a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ data = {
+ "alias": alias,
+ "isDefault": is_default
+ }
+
+ r = client.put("api/v1/bitbucket/configuration/" + alias, data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Validate a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/bitbucket/configuration/validate/" + alias)
+ print_json(data=r)
+
+@app.command()
+def validate_all(
+ ctx: typer.Context,
+):
+ """
+ Validate all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/bitbucket/configuration/validate")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/bugsnag.py b/cortexapps_cli/commands/integrations_commands/bugsnag.py
new file mode 100644
index 0000000..a15d226
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/bugsnag.py
@@ -0,0 +1,85 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Bugsnag commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ auth_token: str = typer.Option(None, "--auth-token", "-at", help="Auth token"),
+ organization_id: str = typer.Option(None, "--organization-id", "-o", help="Organization ID"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if auth_token or organization_id:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "authToken": auth_token,
+ "organizationId": organization_id,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/bugsnag/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/bugsnag/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ auth_token: str = typer.Option(None, "--auth-token", "-at", help="Auth token"),
+ organization_id: str = typer.Option(None, "--organization-id", "-o", help="Organization ID"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if auth_token or organization_id:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "authToken": auth_token,
+ "organizationId": organization_id,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/bugsnag/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/bugsnag/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/bugsnag/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/buildkite.py b/cortexapps_cli/commands/integrations_commands/buildkite.py
new file mode 100644
index 0000000..c5b0b31
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/buildkite.py
@@ -0,0 +1,85 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Buildkite commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ organization_slug: str = typer.Option(None, "--organization-slug", "-o", help="Organization slug"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token or organization_slug:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ "organizationSlug": organization_slug,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/buildkite/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/buildkite/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ organization_slug: str = typer.Option(None, "--organization-slug", "-o", help="Organization slug"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token or organization_slug:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ "organizationSlug": organization_slug,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/buildkite/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/buildkite/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/buildkite/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/checkmarx_sast.py b/cortexapps_cli/commands/integrations_commands/checkmarx_sast.py
new file mode 100644
index 0000000..75d1258
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/checkmarx_sast.py
@@ -0,0 +1,85 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Checkmarx SAST commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ username: str = typer.Option(None, "--username", "-u", help="Username"),
+ password: str = typer.Option(None, "--password", "-p", help="Password"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if username or password:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "username": username,
+ "password": password,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/checkmarx/sast/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/checkmarx/sast/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ username: str = typer.Option(None, "--username", "-u", help="Username"),
+ password: str = typer.Option(None, "--password", "-p", help="Password"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if username or password:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "username": username,
+ "password": password,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/checkmarx/sast/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/checkmarx/sast/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/checkmarx/sast/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/clickup.py b/cortexapps_cli/commands/integrations_commands/clickup.py
new file mode 100644
index 0000000..f787925
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/clickup.py
@@ -0,0 +1,81 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="ClickUp commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ personal_api_token: str = typer.Option(None, "--personal-api-token", "-pat", help="Personal API token"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if personal_api_token:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "personalApiToken": personal_api_token,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/clickup/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/clickup/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ personal_api_token: str = typer.Option(None, "--personal-api-token", "-pat", help="Personal API token"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if personal_api_token:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "personalApiToken": personal_api_token,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/clickup/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/clickup/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/clickup/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/codecov.py b/cortexapps_cli/commands/integrations_commands/codecov.py
new file mode 100644
index 0000000..e39a914
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/codecov.py
@@ -0,0 +1,81 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Codecov commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/codecov/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/codecov/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/codecov/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/codecov/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/codecov/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/dynatrace.py b/cortexapps_cli/commands/integrations_commands/dynatrace.py
new file mode 100644
index 0000000..cfbe596
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/dynatrace.py
@@ -0,0 +1,85 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Dynatrace commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ api_key: str = typer.Option(None, "--api-key", "-api", help="API key"),
+ domain: str = typer.Option(None, "--domain", "-d", help="Domain"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_key or domain:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiKey": api_key,
+ "domain": domain,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/dynatrace/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/dynatrace/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ api_key: str = typer.Option(None, "--api-key", "-api", help="API key"),
+ domain: str = typer.Option(None, "--domain", "-d", help="Domain"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_key or domain:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiKey": api_key,
+ "domain": domain,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/dynatrace/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/dynatrace/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/dynatrace/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/firehydrant.py b/cortexapps_cli/commands/integrations_commands/firehydrant.py
new file mode 100644
index 0000000..245ca59
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/firehydrant.py
@@ -0,0 +1,81 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Firehydrant commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/firehydrant/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/firehydrant/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/firehydrant/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/firehydrant/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/firehydrant/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/instana.py b/cortexapps_cli/commands/integrations_commands/instana.py
new file mode 100644
index 0000000..61ac3fc
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/instana.py
@@ -0,0 +1,85 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Instana commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ endpoint: str = typer.Option(None, "--endpoint", "-e", help="Endpoint"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token or endpoint:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ "endpoint": endpoint,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/instana/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/instana/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ endpoint: str = typer.Option(None, "--endpoint", "-e", help="Endpoint"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token or endpoint:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ "endpoint": endpoint,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/instana/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/instana/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/instana/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/jenkins.py b/cortexapps_cli/commands/integrations_commands/jenkins.py
new file mode 100644
index 0000000..fa5ddec
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/jenkins.py
@@ -0,0 +1,171 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Jenkins commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="Alias for this configuration"),
+ api_key: str = typer.Option(..., "--api-key", "-api", help="API key"),
+ host: str = typer.Option(..., "--host", "-h", help="Host name"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+ username: str = typer.Option(..., "--username", "-u", help="Username"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a single configuration
+ """
+
+ client = ctx.obj["client"]
+
+ if file_input:
+ if alias or api_key or host or is_default or username:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "alias": alias,
+ "apiKey": api_key,
+ "host": host,
+ "isDefault": is_default,
+ "username": username,
+ }
+
+ # remove any data elements that are None
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/jenkins/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def add_multiple(
+ ctx: typer.Context,
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add multiple configurations
+ """
+
+ client = ctx.obj["client"]
+
+ data = json.loads("".join([line for line in file_input]))
+
+ r = client.put("api/v1/jenkins/configurations", data=data)
+ print_json(data=r)
+
+@app.command()
+def delete(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Delete a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/jenkins/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def delete_all(
+ ctx: typer.Context,
+):
+ """
+ Delete all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/jenkins/configurations")
+ print_json(data=r)
+
+@app.command()
+def get(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Get a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/jenkins/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def list(
+ ctx: typer.Context,
+):
+ """
+ Get all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/jenkins/configurations")
+ print_json(data=r)
+
+@app.command()
+def get_default(
+ ctx: typer.Context,
+):
+ """
+ Get default configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/jenkins/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+):
+ """
+ Update a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ data = {
+ "alias": alias,
+ "isDefault": is_default
+ }
+
+ r = client.put("api/v1/jenkins/configuration/" + alias, data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Validate a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/jenkins/configuration/validate/" + alias)
+ print_json(data=r)
+
+@app.command()
+def validate_all(
+ ctx: typer.Context,
+):
+ """
+ Validate all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/jenkins/configuration/validate")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/jira.py b/cortexapps_cli/commands/integrations_commands/jira.py
new file mode 100644
index 0000000..ef91741
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/jira.py
@@ -0,0 +1,152 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Jira commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a single configuration (use --file for JSON input)
+ """
+
+ client = ctx.obj["client"]
+
+ data = json.loads("".join([line for line in file_input]))
+
+ r = client.post("api/v1/jira/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def add_multiple(
+ ctx: typer.Context,
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add multiple configurations
+ """
+
+ client = ctx.obj["client"]
+
+ data = json.loads("".join([line for line in file_input]))
+
+ r = client.put("api/v1/jira/configurations", data=data)
+ print_json(data=r)
+
+@app.command()
+def delete(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Delete a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/jira/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def delete_all(
+ ctx: typer.Context,
+):
+ """
+ Delete all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/jira/configurations")
+ print_json(data=r)
+
+@app.command()
+def get(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Get a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/jira/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def list(
+ ctx: typer.Context,
+):
+ """
+ Get all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/jira/configurations")
+ print_json(data=r)
+
+@app.command()
+def get_default(
+ ctx: typer.Context,
+):
+ """
+ Get default configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/jira/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+):
+ """
+ Update a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ data = {
+ "alias": alias,
+ "isDefault": is_default
+ }
+
+ r = client.put("api/v1/jira/configuration/" + alias, data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Validate a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/jira/configuration/validate/" + alias)
+ print_json(data=r)
+
+@app.command()
+def validate_all(
+ ctx: typer.Context,
+):
+ """
+ Validate all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/jira/configuration/validate")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/mend_sast.py b/cortexapps_cli/commands/integrations_commands/mend_sast.py
new file mode 100644
index 0000000..1279ff2
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/mend_sast.py
@@ -0,0 +1,81 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Mend SAST commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ api_key: str = typer.Option(None, "--api-key", "-api", help="API key"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_key:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiKey": api_key,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/mend/sast/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/mend/sast/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ api_key: str = typer.Option(None, "--api-key", "-api", help="API key"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_key:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiKey": api_key,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/mend/sast/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/mend/sast/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/mend/sast/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/mend_sca.py b/cortexapps_cli/commands/integrations_commands/mend_sca.py
new file mode 100644
index 0000000..d32da10
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/mend_sca.py
@@ -0,0 +1,93 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Mend SCA commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ org_key: str = typer.Option(None, "--org-key", "-ok", help="Organization key"),
+ user_key: str = typer.Option(None, "--user-key", "-uk", help="User key"),
+ org_type: str = typer.Option(None, "--org-type", "-ot", help="Organization type"),
+ url_type: str = typer.Option(None, "--url-type", "-ut", help="URL type"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if org_key or user_key or org_type or url_type:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "orgKey": org_key,
+ "userKey": user_key,
+ "orgType": org_type,
+ "urlType": url_type,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/mend/sca/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/mend/sca/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ org_key: str = typer.Option(None, "--org-key", "-ok", help="Organization key"),
+ user_key: str = typer.Option(None, "--user-key", "-uk", help="User key"),
+ org_type: str = typer.Option(None, "--org-type", "-ot", help="Organization type"),
+ url_type: str = typer.Option(None, "--url-type", "-ut", help="URL type"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if org_key or user_key or org_type or url_type:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "orgKey": org_key,
+ "userKey": user_key,
+ "orgType": org_type,
+ "urlType": url_type,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/mend/sca/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/mend/sca/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/mend/sca/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/okta.py b/cortexapps_cli/commands/integrations_commands/okta.py
new file mode 100644
index 0000000..0aa5fa4
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/okta.py
@@ -0,0 +1,85 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Okta commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ domain: str = typer.Option(None, "--domain", "-d", help="Domain"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token or domain:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ "domain": domain,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/okta/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/okta/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ domain: str = typer.Option(None, "--domain", "-d", help="Domain"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token or domain:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ "domain": domain,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/okta/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/okta/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/okta/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/opsgenie.py b/cortexapps_cli/commands/integrations_commands/opsgenie.py
new file mode 100644
index 0000000..51d9acb
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/opsgenie.py
@@ -0,0 +1,89 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Opsgenie commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ is_eu: bool = typer.Option(False, "--is-eu", "-eu", help="If the Opsgenie instance is in the EU region"),
+ subdomain: str = typer.Option(None, "--subdomain", "-s", help="Subdomain"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token or subdomain:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ "isEu": is_eu,
+ "subdomain": subdomain,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/opsgenie/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/opsgenie/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ api_token: str = typer.Option(None, "--api-token", "-at", help="API token"),
+ is_eu: bool = typer.Option(False, "--is-eu", "-eu", help="If the Opsgenie instance is in the EU region"),
+ subdomain: str = typer.Option(None, "--subdomain", "-s", help="Subdomain"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_token or subdomain:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiToken": api_token,
+ "isEu": is_eu,
+ "subdomain": subdomain,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/opsgenie/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/opsgenie/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/opsgenie/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/rollbar.py b/cortexapps_cli/commands/integrations_commands/rollbar.py
new file mode 100644
index 0000000..1a98f31
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/rollbar.py
@@ -0,0 +1,85 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Rollbar commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ access_token: str = typer.Option(None, "--access-token", "-at", help="Access token"),
+ organization_slug: str = typer.Option(None, "--organization-slug", "-o", help="Organization slug"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if access_token or organization_slug:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "accessToken": access_token,
+ "organizationSlug": organization_slug,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/rollbar/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/rollbar/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ access_token: str = typer.Option(None, "--access-token", "-at", help="Access token"),
+ organization_slug: str = typer.Option(None, "--organization-slug", "-o", help="Organization slug"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if access_token or organization_slug:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "accessToken": access_token,
+ "organizationSlug": organization_slug,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/rollbar/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/rollbar/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/rollbar/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/rootly.py b/cortexapps_cli/commands/integrations_commands/rootly.py
new file mode 100644
index 0000000..84426ac
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/rootly.py
@@ -0,0 +1,167 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Rootly commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="Alias for this configuration"),
+ api_key: str = typer.Option(..., "--api-key", "-api", help="API key"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a single configuration
+ """
+
+ client = ctx.obj["client"]
+
+ if file_input:
+ if alias or api_key or is_default:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "alias": alias,
+ "apiKey": api_key,
+ "isDefault": is_default,
+ }
+
+ # remove any data elements that are None
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/rootly/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def add_multiple(
+ ctx: typer.Context,
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add multiple configurations
+ """
+
+ client = ctx.obj["client"]
+
+ data = json.loads("".join([line for line in file_input]))
+
+ r = client.put("api/v1/rootly/configurations", data=data)
+ print_json(data=r)
+
+@app.command()
+def delete(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Delete a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/rootly/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def delete_all(
+ ctx: typer.Context,
+):
+ """
+ Delete all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/rootly/configurations")
+ print_json(data=r)
+
+@app.command()
+def get(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Get a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/rootly/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def list(
+ ctx: typer.Context,
+):
+ """
+ Get all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/rootly/configurations")
+ print_json(data=r)
+
+@app.command()
+def get_default(
+ ctx: typer.Context,
+):
+ """
+ Get default configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/rootly/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+):
+ """
+ Update a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ data = {
+ "alias": alias,
+ "isDefault": is_default
+ }
+
+ r = client.put("api/v1/rootly/configuration/" + alias, data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Validate a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/rootly/configuration/validate/" + alias)
+ print_json(data=r)
+
+@app.command()
+def validate_all(
+ ctx: typer.Context,
+):
+ """
+ Validate all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/rootly/configuration/validate")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/semgrep.py b/cortexapps_cli/commands/integrations_commands/semgrep.py
new file mode 100644
index 0000000..5186f75
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/semgrep.py
@@ -0,0 +1,171 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Semgrep commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="Alias for this configuration"),
+ api_key: str = typer.Option(..., "--api-key", "-api", help="API key"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+ organization_id: str = typer.Option(..., "--organization-id", "-oi", help="Organization ID"),
+ organization_slug: str = typer.Option(..., "--organization-slug", "-os", help="Organization slug"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a single configuration
+ """
+
+ client = ctx.obj["client"]
+
+ if file_input:
+ if alias or api_key or is_default or organization_id or organization_slug:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "alias": alias,
+ "apiKey": api_key,
+ "isDefault": is_default,
+ "organizationId": organization_id,
+ "organizationSlug": organization_slug,
+ }
+
+ # remove any data elements that are None
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/semgrep/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def add_multiple(
+ ctx: typer.Context,
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configurations; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add multiple configurations
+ """
+
+ client = ctx.obj["client"]
+
+ data = json.loads("".join([line for line in file_input]))
+
+ r = client.put("api/v1/semgrep/configurations", data=data)
+ print_json(data=r)
+
+@app.command()
+def delete(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Delete a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/semgrep/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def delete_all(
+ ctx: typer.Context,
+):
+ """
+ Delete all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.delete("api/v1/semgrep/configurations")
+ print_json(data=r)
+
+@app.command()
+def get(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Get a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/semgrep/configuration/" + alias)
+ print_json(data=r)
+
+@app.command()
+def list(
+ ctx: typer.Context,
+):
+ """
+ Get all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/semgrep/configurations")
+ print_json(data=r)
+
+@app.command()
+def get_default(
+ ctx: typer.Context,
+):
+ """
+ Get default configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.get("api/v1/semgrep/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="If this is the default configuration"),
+):
+ """
+ Update a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ data = {
+ "alias": alias,
+ "isDefault": is_default
+ }
+
+ r = client.put("api/v1/semgrep/configuration/" + alias, data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(
+ ctx: typer.Context,
+ alias: str = typer.Option(..., "--alias", "-a", help="The alias of the configuration"),
+):
+ """
+ Validate a configuration
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/semgrep/configuration/validate/" + alias)
+ print_json(data=r)
+
+@app.command()
+def validate_all(
+ ctx: typer.Context,
+):
+ """
+ Validate all configurations
+ """
+
+ client = ctx.obj["client"]
+
+ r = client.post("api/v1/semgrep/configuration/validate")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/sentry.py b/cortexapps_cli/commands/integrations_commands/sentry.py
new file mode 100644
index 0000000..0f9b4f4
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/sentry.py
@@ -0,0 +1,89 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Sentry commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ auth_token: str = typer.Option(None, "--auth-token", "-at", help="Auth token"),
+ organization_slug: str = typer.Option(None, "--organization-slug", "-o", help="Organization slug"),
+ host: str = typer.Option(None, "--host", "-h", help="Host"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if auth_token or organization_slug or host:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "authToken": auth_token,
+ "organizationSlug": organization_slug,
+ "host": host,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/sentry/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/sentry/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ auth_token: str = typer.Option(None, "--auth-token", "-at", help="Auth token"),
+ organization_slug: str = typer.Option(None, "--organization-slug", "-o", help="Organization slug"),
+ host: str = typer.Option(None, "--host", "-h", help="Host"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if auth_token or organization_slug or host:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "authToken": auth_token,
+ "organizationSlug": organization_slug,
+ "host": host,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/sentry/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/sentry/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/sentry/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/servicenow.py b/cortexapps_cli/commands/integrations_commands/servicenow.py
new file mode 100644
index 0000000..f7f978a
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/servicenow.py
@@ -0,0 +1,89 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="ServiceNow commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ instance_name: str = typer.Option(None, "--instance-name", "-in", help="Instance name"),
+ password: str = typer.Option(None, "--password", "-p", help="Password"),
+ username: str = typer.Option(None, "--username", "-u", help="Username"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if instance_name or password or username:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "instanceName": instance_name,
+ "password": password,
+ "username": username,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/servicenow/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/servicenow/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ instance_name: str = typer.Option(None, "--instance-name", "-in", help="Instance name"),
+ password: str = typer.Option(None, "--password", "-p", help="Password"),
+ username: str = typer.Option(None, "--username", "-u", help="Username"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if instance_name or password or username:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "instanceName": instance_name,
+ "password": password,
+ "username": username,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/servicenow/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/servicenow/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/servicenow/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/servicenow_cloud_observability.py b/cortexapps_cli/commands/integrations_commands/servicenow_cloud_observability.py
new file mode 100644
index 0000000..d40b962
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/servicenow_cloud_observability.py
@@ -0,0 +1,89 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="ServiceNow Cloud Observability commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ auth_token: str = typer.Option(None, "--auth-token", "-at", help="Auth token"),
+ organization_id: str = typer.Option(None, "--organization-id", "-o", help="Organization ID"),
+ project_id: str = typer.Option(None, "--project-id", "-pi", help="Project ID"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if auth_token or organization_id or project_id:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "authToken": auth_token,
+ "organizationId": organization_id,
+ "projectId": project_id,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/lightstep/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/lightstep/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ auth_token: str = typer.Option(None, "--auth-token", "-at", help="Auth token"),
+ organization_id: str = typer.Option(None, "--organization-id", "-o", help="Organization ID"),
+ project_id: str = typer.Option(None, "--project-id", "-pi", help="Project ID"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if auth_token or organization_id or project_id:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "authToken": auth_token,
+ "organizationId": organization_id,
+ "projectId": project_id,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/lightstep/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/lightstep/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/lightstep/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/snyk.py b/cortexapps_cli/commands/integrations_commands/snyk.py
new file mode 100644
index 0000000..bf2f06f
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/snyk.py
@@ -0,0 +1,92 @@
+import json
+from enum import Enum
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+class Region(str, Enum):
+ USA = "USA"
+ US2 = "US2"
+ EU = "EU"
+ AUS = "AUS"
+
+app = typer.Typer(help="Snyk commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ auth_token: str = typer.Option(None, "--auth-token", "-at", help="Auth token"),
+ region: Region = typer.Option(None, "--region", "-r", help="Region"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if auth_token or region:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "authToken": auth_token,
+ "region": region.value if region else None,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/snyk/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/snyk/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ auth_token: str = typer.Option(None, "--auth-token", "-at", help="Auth token"),
+ region: Region = typer.Option(None, "--region", "-r", help="Region"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if auth_token or region:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "authToken": auth_token,
+ "region": region.value if region else None,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/snyk/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/snyk/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/snyk/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/splunk_observability_cloud.py b/cortexapps_cli/commands/integrations_commands/splunk_observability_cloud.py
new file mode 100644
index 0000000..62372b9
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/splunk_observability_cloud.py
@@ -0,0 +1,85 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Splunk Observability Cloud commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ access_token: str = typer.Option(None, "--access-token", "-at", help="Access token"),
+ realm: str = typer.Option(None, "--realm", "-r", help="Realm"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if access_token or realm:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "accessToken": access_token,
+ "realm": realm,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/signalfx/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/signalfx/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ access_token: str = typer.Option(None, "--access-token", "-at", help="Access token"),
+ realm: str = typer.Option(None, "--realm", "-r", help="Realm"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if access_token or realm:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "accessToken": access_token,
+ "realm": realm,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/signalfx/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/signalfx/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/signalfx/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/splunk_on_call.py b/cortexapps_cli/commands/integrations_commands/splunk_on_call.py
new file mode 100644
index 0000000..5fff998
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/splunk_on_call.py
@@ -0,0 +1,89 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Splunk On Call commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ api_id: str = typer.Option(None, "--api-id", "-ai", help="API ID"),
+ api_key: str = typer.Option(None, "--api-key", "-api", help="API key"),
+ organization: str = typer.Option(None, "--organization", "-o", help="Organization"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_id or api_key or organization:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiId": api_id,
+ "apiKey": api_key,
+ "organization": organization,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/victorops/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/victorops/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ api_id: str = typer.Option(None, "--api-id", "-ai", help="API ID"),
+ api_key: str = typer.Option(None, "--api-key", "-api", help="API key"),
+ organization: str = typer.Option(None, "--organization", "-o", help="Organization"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_id or api_key or organization:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiId": api_id,
+ "apiKey": api_key,
+ "organization": organization,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/victorops/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/victorops/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/victorops/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/sumologic.py b/cortexapps_cli/commands/integrations_commands/sumologic.py
new file mode 100644
index 0000000..53d15a8
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/sumologic.py
@@ -0,0 +1,89 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Sumo Logic commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ access_id: str = typer.Option(None, "--access-id", "-ai", help="Access ID"),
+ access_key: str = typer.Option(None, "--access-key", "-ak", help="Access key"),
+ deployment: str = typer.Option(None, "--deployment", "-d", help="Deployment"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if access_id or access_key or deployment:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "accessId": access_id,
+ "accessKey": access_key,
+ "deployment": deployment,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/sumologic/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/sumologic/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ access_id: str = typer.Option(None, "--access-id", "-ai", help="Access ID"),
+ access_key: str = typer.Option(None, "--access-key", "-ak", help="Access key"),
+ deployment: str = typer.Option(None, "--deployment", "-d", help="Deployment"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if access_id or access_key or deployment:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "accessId": access_id,
+ "accessKey": access_key,
+ "deployment": deployment,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/sumologic/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/sumologic/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/sumologic/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/veracode.py b/cortexapps_cli/commands/integrations_commands/veracode.py
new file mode 100644
index 0000000..f2c55f8
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/veracode.py
@@ -0,0 +1,89 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Veracode commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ api_key: str = typer.Option(None, "--api-key", "-api", help="API key"),
+ key_id: str = typer.Option(None, "--key-id", "-ki", help="Key ID"),
+ region: str = typer.Option(None, "--region", "-r", help="Region"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_key or key_id or region:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiKey": api_key,
+ "keyId": key_id,
+ "region": region,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/veracode/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/veracode/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ api_key: str = typer.Option(None, "--api-key", "-api", help="API key"),
+ key_id: str = typer.Option(None, "--key-id", "-ki", help="Key ID"),
+ region: str = typer.Option(None, "--region", "-r", help="Region"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if api_key or key_id or region:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "apiKey": api_key,
+ "keyId": key_id,
+ "region": region,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/veracode/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/veracode/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/veracode/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/wiz.py b/cortexapps_cli/commands/integrations_commands/wiz.py
new file mode 100644
index 0000000..3dc4a91
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/wiz.py
@@ -0,0 +1,93 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Wiz commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ client_id: str = typer.Option(None, "--client-id", "-ci", help="Client ID"),
+ client_secret: str = typer.Option(None, "--client-secret", "-cs", help="Client secret"),
+ data_center: str = typer.Option(None, "--data-center", "-dc", help="Data center"),
+ identity_provider: str = typer.Option(None, "--identity-provider", "-ip", help="Identity provider"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if client_id or client_secret or data_center or identity_provider:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "clientId": client_id,
+ "clientSecret": client_secret,
+ "dataCenter": data_center,
+ "identityProvider": identity_provider,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/wiz/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/wiz/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ client_id: str = typer.Option(None, "--client-id", "-ci", help="Client ID"),
+ client_secret: str = typer.Option(None, "--client-secret", "-cs", help="Client secret"),
+ data_center: str = typer.Option(None, "--data-center", "-dc", help="Data center"),
+ identity_provider: str = typer.Option(None, "--identity-provider", "-ip", help="Identity provider"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if client_id or client_secret or data_center or identity_provider:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "clientId": client_id,
+ "clientSecret": client_secret,
+ "dataCenter": data_center,
+ "identityProvider": identity_provider,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/wiz/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/wiz/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/wiz/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/workday.py b/cortexapps_cli/commands/integrations_commands/workday.py
new file mode 100644
index 0000000..bc9d28e
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/workday.py
@@ -0,0 +1,89 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="Workday commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ username: str = typer.Option(None, "--username", "-u", help="Username"),
+ password: str = typer.Option(None, "--password", "-p", help="Password"),
+ ownership_report_url: str = typer.Option(None, "--ownership-report-url", "-url", help="Ownership report URL"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if username or password or ownership_report_url:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "username": username,
+ "password": password,
+ "ownershipReportUrl": ownership_report_url,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/workday/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/workday/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ username: str = typer.Option(None, "--username", "-u", help="Username"),
+ password: str = typer.Option(None, "--password", "-p", help="Password"),
+ ownership_report_url: str = typer.Option(None, "--ownership-report-url", "-url", help="Ownership report URL"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if username or password or ownership_report_url:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "username": username,
+ "password": password,
+ "ownershipReportUrl": ownership_report_url,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/workday/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/workday/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/workday/configurations")
+ print_json(data=r)
diff --git a/cortexapps_cli/commands/integrations_commands/xmatters.py b/cortexapps_cli/commands/integrations_commands/xmatters.py
new file mode 100644
index 0000000..f37b5a1
--- /dev/null
+++ b/cortexapps_cli/commands/integrations_commands/xmatters.py
@@ -0,0 +1,89 @@
+import json
+from rich import print_json
+import typer
+from typing_extensions import Annotated
+
+app = typer.Typer(help="xMatters commands", no_args_is_help=True)
+
+@app.command()
+def add(
+ ctx: typer.Context,
+ organization_slug: str = typer.Option(None, "--organization-slug", "-o", help="Organization slug"),
+ password: str = typer.Option(None, "--password", "-p", help="Password"),
+ username: str = typer.Option(None, "--username", "-u", help="Username"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Add a configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if organization_slug or password or username:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "organizationSlug": organization_slug,
+ "password": password,
+ "username": username,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.post("api/v1/xmatters/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def get(ctx: typer.Context):
+ """
+ Get the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.get("api/v1/xmatters/default-configuration")
+ print_json(data=r)
+
+@app.command()
+def update(
+ ctx: typer.Context,
+ organization_slug: str = typer.Option(None, "--organization-slug", "-o", help="Organization slug"),
+ password: str = typer.Option(None, "--password", "-p", help="Password"),
+ username: str = typer.Option(None, "--username", "-u", help="Username"),
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration, if command line options not used; can be passed as stdin with -, example: -f-")] = None,
+):
+ """
+ Update the configuration
+ """
+ client = ctx.obj["client"]
+
+ if file_input:
+ if organization_slug or password or username:
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
+ data = json.loads("".join([line for line in file_input]))
+ else:
+ data = {
+ "organizationSlug": organization_slug,
+ "password": password,
+ "username": username,
+ }
+ data = {k: v for k, v in data.items() if v is not None}
+
+ r = client.put("api/v1/xmatters/configuration", data=data)
+ print_json(data=r)
+
+@app.command()
+def validate(ctx: typer.Context):
+ """
+ Validate the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.post("api/v1/xmatters/configuration/validate")
+ print_json(data=r)
+
+@app.command()
+def delete(ctx: typer.Context):
+ """
+ Delete the configuration
+ """
+ client = ctx.obj["client"]
+ r = client.delete("api/v1/xmatters/configurations")
+ print_json(data=r)
diff --git a/docs/superpowers/specs/2026-05-20-new-integrations-design.md b/docs/superpowers/specs/2026-05-20-new-integrations-design.md
new file mode 100644
index 0000000..60c76a2
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-20-new-integrations-design.md
@@ -0,0 +1,102 @@
+# Add 32 New Integration Commands
+
+**Date**: 2026-05-20
+**Version target**: 1.17.0 (minor bump via `feat:` prefix)
+
+## Summary
+
+Add CLI support for 32 integrations that have API endpoints but no CLI commands yet. Each integration gets its own command file following the existing patterns in `integrations_commands/`.
+
+## Two Patterns
+
+### Multi-config (alias-based) — 7 integrations
+
+Commands: `add`, `add-multiple`, `get`, `list`, `get-default`, `update`, `delete`, `delete-all`, `validate`, `validate-all`
+
+Modeled after existing `incidentio.py` / `pagerduty.py` pattern.
+
+| Integration | CLI name | API path | Add fields |
+|---|---|---|---|
+| Apiiro | `apiiro` | `apiiro` | alias, apiKey, isDefault |
+| ArgoCD | `argocd` | `argocd` | alias, host, isDefault, password, username |
+| Bitbucket | `bitbucket` | `bitbucket` | alias, isDefault, type + auth fields (4 types) |
+| Jenkins | `jenkins` | `jenkins` | alias, apiKey, host, isDefault, username |
+| Jira | `jira` | `jira` | alias, isDefault, type + auth fields (3 types) |
+| Rootly | `rootly` | `rootly` | alias, apiKey, isDefault |
+| Semgrep | `semgrep` | `semgrep` | alias, apiKey, isDefault, organizationId, organizationSlug |
+
+### Single-config — 25 integrations
+
+Commands: `add`, `get`, `update`, `validate`, `delete`
+
+No alias support. Simpler CRUD pattern.
+
+| Integration | CLI name | API path | Add fields |
+|---|---|---|---|
+| Azure AD (Entra ID) | `azure-active-directory` | `active-directory` | clientId, clientSecret, tenantId |
+| BambooHR | `bamboohr` | `bamboohr` | apiToken, subdomain |
+| Bugsnag | `bugsnag` | `bugsnag` | authToken, organizationId |
+| Buildkite | `buildkite` | `buildkite` | apiToken, organizationSlug |
+| Checkmarx SAST | `checkmarx-sast` | `checkmarx/sast` | username, password |
+| ClickUp | `clickup` | `clickup` | personalApiToken |
+| Codecov | `codecov` | `codecov` | apiToken |
+| Dynatrace | `dynatrace` | `dynatrace` | apiKey, domain |
+| Firehydrant | `firehydrant` | `firehydrant` | apiToken |
+| Instana | `instana` | `instana` | apiToken, endpoint |
+| Mend SAST | `mend-sast` | `mend/sast` | apiKey |
+| Mend SCA | `mend-sca` | `mend/sca` | orgKey, userKey, orgType, urlType |
+| Okta | `okta` | `okta` | apiToken, domain |
+| Opsgenie | `opsgenie` | `opsgenie` | apiToken, isEu, subdomain |
+| Rollbar | `rollbar` | `rollbar` | accessToken, organizationSlug |
+| Sentry | `sentry` | `sentry` | authToken, organizationSlug, host (optional) |
+| ServiceNow | `servicenow` | `servicenow` | instanceName, password, username |
+| ServiceNow Cloud Obs | `servicenow-cloud-observability` | `lightstep` | authToken, organizationId, projectId |
+| Snyk | `snyk` | `snyk` | authToken, region (enum: USA/US2/EU/AUS) |
+| Splunk Obs Cloud | `splunk-observability-cloud` | `signalfx` | accessToken, realm |
+| Splunk On Call | `splunk-on-call` | `victorops` | apiId, apiKey, organization |
+| Sumo Logic | `sumo-logic` | `sumologic` | accessId, accessKey, deployment |
+| Veracode | `veracode` | `veracode` | apiKey, keyId, region |
+| Wiz | `wiz` | `wiz` | clientId, clientSecret, dataCenter, identityProvider |
+| Workday | `workday` | `workday` | username, password, ownershipReportUrl |
+| xMatters | `xmatters` | `xmatters` | organizationSlug, password, username |
+
+## Single-config command details
+
+- **`add`**: POST `/api/v1/{path}/configuration`. Accepts `--file/-f` for JSON input OR individual flags. Raises `BadParameter` if both provided.
+- **`get`**: GET `/api/v1/{path}/default-configuration`
+- **`update`**: PUT `/api/v1/{path}/configuration`. Same flags as add.
+- **`validate`**: POST `/api/v1/{path}/configuration/validate`
+- **`delete`**: DELETE `/api/v1/{path}/configurations`
+
+## Multi-config command details
+
+Same as existing multi-config integrations (incidentio, pagerduty, coralogix pattern):
+- **`add`**: POST `/api/v1/{path}/configuration` with `--alias/-a`, `--is-default/-i`, integration-specific flags, or `--file/-f`
+- **`add-multiple`**: POST `/api/v1/{path}/configurations` from `--file/-f`
+- **`get`**: GET `/api/v1/{path}/configuration/{alias}` with `--alias/-a`
+- **`list`**: GET `/api/v1/{path}/configurations`
+- **`get-default`**: GET `/api/v1/{path}/default-configuration`
+- **`update`**: PUT `/api/v1/{path}/configuration/{alias}` with `--alias/-a`, `--is-default/-i`
+- **`delete`**: DELETE `/api/v1/{path}/configuration/{alias}` with `--alias/-a`
+- **`delete-all`**: DELETE `/api/v1/{path}/configurations`
+- **`validate`**: POST `/api/v1/{path}/configuration/validate/{alias}` with `--alias/-a`
+- **`validate-all`**: POST `/api/v1/{path}/configuration/validate`
+
+## Special cases
+
+- **Bitbucket**: 4 auth types (cloud-atlassian, cloud-personal, on-prem-basic, workspace-token). Use `--file/-f` for add; CLI flags not practical for polymorphic types.
+- **Jira**: 3 auth types (cloud-basic, cloud-scoped, on-prem-basic). Same approach — `--file/-f` for add.
+- **Snyk**: Region enum (USA, US2, EU, AUS)
+- **Veracode**: Region field (string, not enum — passed through)
+- **Opsgenie**: `--is-eu` boolean flag
+- **Mend**: Two separate sub-integrations (SAST and SCA) each get their own command file
+
+## File changes
+
+1. **32 new files** in `cortexapps_cli/commands/integrations_commands/`
+2. **Update** `cortexapps_cli/commands/integrations.py` — add 32 imports and `add_typer()` calls
+3. **32 new test files** in `tests/` following existing `test_integrations_*.py` pattern with `responses` mocking
+
+## Testing
+
+Each integration gets a test file with mocked HTTP responses (using `responses` library). Tests verify CLI invocation reaches the correct API endpoints with correct HTTP methods. No real API calls needed.
diff --git a/tests/test_integrations_apiiro.py b/tests/test_integrations_apiiro.py
new file mode 100644
index 0000000..91d148f
--- /dev/null
+++ b/tests/test_integrations_apiiro.py
@@ -0,0 +1,59 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_apiiro_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_apiiro_add():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/apiiro/configuration", json={}, status=200)
+ cli(["integrations", "apiiro", "add", "-a", "myAlias", "--api-key", "123456", "-i"])
+
+@responses.activate
+def test_integrations_apiiro_add_multiple(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/apiiro/configurations", json={}, status=200)
+ cli(["integrations", "apiiro", "add-multiple", "-f", str(f)])
+
+@responses.activate
+def test_integrations_apiiro_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/apiiro/configuration/test", status=200)
+ cli(["integrations", "apiiro", "delete", "-a", "test"])
+
+@responses.activate
+def test_integrations_apiiro_delete_all():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/apiiro/configurations", status=200)
+ cli(["integrations", "apiiro", "delete-all"])
+
+@responses.activate
+def test_integrations_apiiro_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/apiiro/configuration/test", json={}, status=200)
+ cli(["integrations", "apiiro", "get", "-a", "test"])
+
+@responses.activate
+def test_integrations_apiiro_list():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/apiiro/configurations", json={}, status=200)
+ cli(["integrations", "apiiro", "list"])
+
+@responses.activate
+def test_integrations_apiiro_get_default():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/apiiro/default-configuration", json={}, status=200)
+ cli(["integrations", "apiiro", "get-default"])
+
+@responses.activate
+def test_integrations_apiiro_update():
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/apiiro/configuration/test", json={}, status=200)
+ cli(["integrations", "apiiro", "update", "-a", "test", "-i"])
+
+@responses.activate
+def test_integrations_apiiro_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/apiiro/configuration/validate/test", json={}, status=200)
+ cli(["integrations", "apiiro", "validate", "-a", "test"])
+
+@responses.activate
+def test_integrations_apiiro_validate_all():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/apiiro/configuration/validate", json={}, status=200)
+ cli(["integrations", "apiiro", "validate-all"])
diff --git a/tests/test_integrations_argocd.py b/tests/test_integrations_argocd.py
new file mode 100644
index 0000000..511f9ad
--- /dev/null
+++ b/tests/test_integrations_argocd.py
@@ -0,0 +1,59 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_argocd_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_argocd_add():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/argocd/configuration", json={}, status=200)
+ cli(["integrations", "argocd", "add", "-a", "myAlias", "-h", "https://argocd.example.com", "-p", "pass123", "-u", "admin", "-i"])
+
+@responses.activate
+def test_integrations_argocd_add_multiple(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/argocd/configurations", json={}, status=200)
+ cli(["integrations", "argocd", "add-multiple", "-f", str(f)])
+
+@responses.activate
+def test_integrations_argocd_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/argocd/configuration/test", status=200)
+ cli(["integrations", "argocd", "delete", "-a", "test"])
+
+@responses.activate
+def test_integrations_argocd_delete_all():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/argocd/configurations", status=200)
+ cli(["integrations", "argocd", "delete-all"])
+
+@responses.activate
+def test_integrations_argocd_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/argocd/configuration/test", json={}, status=200)
+ cli(["integrations", "argocd", "get", "-a", "test"])
+
+@responses.activate
+def test_integrations_argocd_list():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/argocd/configurations", json={}, status=200)
+ cli(["integrations", "argocd", "list"])
+
+@responses.activate
+def test_integrations_argocd_get_default():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/argocd/default-configuration", json={}, status=200)
+ cli(["integrations", "argocd", "get-default"])
+
+@responses.activate
+def test_integrations_argocd_update():
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/argocd/configuration/test", json={}, status=200)
+ cli(["integrations", "argocd", "update", "-a", "test", "-i"])
+
+@responses.activate
+def test_integrations_argocd_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/argocd/configuration/validate/test", json={}, status=200)
+ cli(["integrations", "argocd", "validate", "-a", "test"])
+
+@responses.activate
+def test_integrations_argocd_validate_all():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/argocd/configuration/validate", json={}, status=200)
+ cli(["integrations", "argocd", "validate-all"])
diff --git a/tests/test_integrations_azure_active_directory.py b/tests/test_integrations_azure_active_directory.py
new file mode 100644
index 0000000..24e8c00
--- /dev/null
+++ b/tests/test_integrations_azure_active_directory.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_azure_active_directory_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_azure_active_directory_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/active-directory/configuration", json={}, status=200)
+ cli(["integrations", "azure-active-directory", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_azure_active_directory_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/active-directory/default-configuration", json={}, status=200)
+ cli(["integrations", "azure-active-directory", "get"])
+
+@responses.activate
+def test_integrations_azure_active_directory_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/active-directory/configuration", json={}, status=200)
+ cli(["integrations", "azure-active-directory", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_azure_active_directory_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/active-directory/configuration/validate", json={}, status=200)
+ cli(["integrations", "azure-active-directory", "validate"])
+
+@responses.activate
+def test_integrations_azure_active_directory_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/active-directory/configurations", json={}, status=200)
+ cli(["integrations", "azure-active-directory", "delete"])
diff --git a/tests/test_integrations_bamboohr.py b/tests/test_integrations_bamboohr.py
new file mode 100644
index 0000000..d04291f
--- /dev/null
+++ b/tests/test_integrations_bamboohr.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_bamboohr_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_bamboohr_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/bamboohr/configuration", json={}, status=200)
+ cli(["integrations", "bamboohr", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_bamboohr_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/bamboohr/default-configuration", json={}, status=200)
+ cli(["integrations", "bamboohr", "get"])
+
+@responses.activate
+def test_integrations_bamboohr_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/bamboohr/configuration", json={}, status=200)
+ cli(["integrations", "bamboohr", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_bamboohr_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/bamboohr/configuration/validate", json={}, status=200)
+ cli(["integrations", "bamboohr", "validate"])
+
+@responses.activate
+def test_integrations_bamboohr_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/bamboohr/configurations", json={}, status=200)
+ cli(["integrations", "bamboohr", "delete"])
diff --git a/tests/test_integrations_bitbucket.py b/tests/test_integrations_bitbucket.py
new file mode 100644
index 0000000..c014858
--- /dev/null
+++ b/tests/test_integrations_bitbucket.py
@@ -0,0 +1,60 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_bitbucket_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_bitbucket_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/bitbucket/configuration", json={}, status=200)
+ cli(["integrations", "bitbucket", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_bitbucket_add_multiple(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/bitbucket/configurations", json={}, status=200)
+ cli(["integrations", "bitbucket", "add-multiple", "-f", str(f)])
+
+@responses.activate
+def test_integrations_bitbucket_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/bitbucket/configuration/test", status=200)
+ cli(["integrations", "bitbucket", "delete", "-a", "test"])
+
+@responses.activate
+def test_integrations_bitbucket_delete_all():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/bitbucket/configurations", status=200)
+ cli(["integrations", "bitbucket", "delete-all"])
+
+@responses.activate
+def test_integrations_bitbucket_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/bitbucket/configuration/test", json={}, status=200)
+ cli(["integrations", "bitbucket", "get", "-a", "test"])
+
+@responses.activate
+def test_integrations_bitbucket_list():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/bitbucket/configurations", json={}, status=200)
+ cli(["integrations", "bitbucket", "list"])
+
+@responses.activate
+def test_integrations_bitbucket_get_default():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/bitbucket/default-configuration", json={}, status=200)
+ cli(["integrations", "bitbucket", "get-default"])
+
+@responses.activate
+def test_integrations_bitbucket_update():
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/bitbucket/configuration/test", json={}, status=200)
+ cli(["integrations", "bitbucket", "update", "-a", "test", "-i"])
+
+@responses.activate
+def test_integrations_bitbucket_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/bitbucket/configuration/validate/test", json={}, status=200)
+ cli(["integrations", "bitbucket", "validate", "-a", "test"])
+
+@responses.activate
+def test_integrations_bitbucket_validate_all():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/bitbucket/configuration/validate", json={}, status=200)
+ cli(["integrations", "bitbucket", "validate-all"])
diff --git a/tests/test_integrations_bugsnag.py b/tests/test_integrations_bugsnag.py
new file mode 100644
index 0000000..5c753ef
--- /dev/null
+++ b/tests/test_integrations_bugsnag.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_bugsnag_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_bugsnag_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/bugsnag/configuration", json={}, status=200)
+ cli(["integrations", "bugsnag", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_bugsnag_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/bugsnag/default-configuration", json={}, status=200)
+ cli(["integrations", "bugsnag", "get"])
+
+@responses.activate
+def test_integrations_bugsnag_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/bugsnag/configuration", json={}, status=200)
+ cli(["integrations", "bugsnag", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_bugsnag_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/bugsnag/configuration/validate", json={}, status=200)
+ cli(["integrations", "bugsnag", "validate"])
+
+@responses.activate
+def test_integrations_bugsnag_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/bugsnag/configurations", json={}, status=200)
+ cli(["integrations", "bugsnag", "delete"])
diff --git a/tests/test_integrations_buildkite.py b/tests/test_integrations_buildkite.py
new file mode 100644
index 0000000..1d97c5c
--- /dev/null
+++ b/tests/test_integrations_buildkite.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_buildkite_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_buildkite_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/buildkite/configuration", json={}, status=200)
+ cli(["integrations", "buildkite", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_buildkite_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/buildkite/default-configuration", json={}, status=200)
+ cli(["integrations", "buildkite", "get"])
+
+@responses.activate
+def test_integrations_buildkite_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/buildkite/configuration", json={}, status=200)
+ cli(["integrations", "buildkite", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_buildkite_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/buildkite/configuration/validate", json={}, status=200)
+ cli(["integrations", "buildkite", "validate"])
+
+@responses.activate
+def test_integrations_buildkite_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/buildkite/configurations", json={}, status=200)
+ cli(["integrations", "buildkite", "delete"])
diff --git a/tests/test_integrations_checkmarx_sast.py b/tests/test_integrations_checkmarx_sast.py
new file mode 100644
index 0000000..a86ad18
--- /dev/null
+++ b/tests/test_integrations_checkmarx_sast.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_checkmarx_sast_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_checkmarx_sast_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/checkmarx/sast/configuration", json={}, status=200)
+ cli(["integrations", "checkmarx-sast", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_checkmarx_sast_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/checkmarx/sast/default-configuration", json={}, status=200)
+ cli(["integrations", "checkmarx-sast", "get"])
+
+@responses.activate
+def test_integrations_checkmarx_sast_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/checkmarx/sast/configuration", json={}, status=200)
+ cli(["integrations", "checkmarx-sast", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_checkmarx_sast_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/checkmarx/sast/configuration/validate", json={}, status=200)
+ cli(["integrations", "checkmarx-sast", "validate"])
+
+@responses.activate
+def test_integrations_checkmarx_sast_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/checkmarx/sast/configurations", json={}, status=200)
+ cli(["integrations", "checkmarx-sast", "delete"])
diff --git a/tests/test_integrations_clickup.py b/tests/test_integrations_clickup.py
new file mode 100644
index 0000000..8318c90
--- /dev/null
+++ b/tests/test_integrations_clickup.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_clickup_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_clickup_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/clickup/configuration", json={}, status=200)
+ cli(["integrations", "clickup", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_clickup_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/clickup/default-configuration", json={}, status=200)
+ cli(["integrations", "clickup", "get"])
+
+@responses.activate
+def test_integrations_clickup_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/clickup/configuration", json={}, status=200)
+ cli(["integrations", "clickup", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_clickup_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/clickup/configuration/validate", json={}, status=200)
+ cli(["integrations", "clickup", "validate"])
+
+@responses.activate
+def test_integrations_clickup_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/clickup/configurations", json={}, status=200)
+ cli(["integrations", "clickup", "delete"])
diff --git a/tests/test_integrations_codecov.py b/tests/test_integrations_codecov.py
new file mode 100644
index 0000000..4eb06a5
--- /dev/null
+++ b/tests/test_integrations_codecov.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_codecov_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_codecov_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/codecov/configuration", json={}, status=200)
+ cli(["integrations", "codecov", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_codecov_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/codecov/default-configuration", json={}, status=200)
+ cli(["integrations", "codecov", "get"])
+
+@responses.activate
+def test_integrations_codecov_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/codecov/configuration", json={}, status=200)
+ cli(["integrations", "codecov", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_codecov_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/codecov/configuration/validate", json={}, status=200)
+ cli(["integrations", "codecov", "validate"])
+
+@responses.activate
+def test_integrations_codecov_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/codecov/configurations", json={}, status=200)
+ cli(["integrations", "codecov", "delete"])
diff --git a/tests/test_integrations_dynatrace.py b/tests/test_integrations_dynatrace.py
new file mode 100644
index 0000000..5201cea
--- /dev/null
+++ b/tests/test_integrations_dynatrace.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_dynatrace_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_dynatrace_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/dynatrace/configuration", json={}, status=200)
+ cli(["integrations", "dynatrace", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_dynatrace_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/dynatrace/default-configuration", json={}, status=200)
+ cli(["integrations", "dynatrace", "get"])
+
+@responses.activate
+def test_integrations_dynatrace_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/dynatrace/configuration", json={}, status=200)
+ cli(["integrations", "dynatrace", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_dynatrace_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/dynatrace/configuration/validate", json={}, status=200)
+ cli(["integrations", "dynatrace", "validate"])
+
+@responses.activate
+def test_integrations_dynatrace_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/dynatrace/configurations", json={}, status=200)
+ cli(["integrations", "dynatrace", "delete"])
diff --git a/tests/test_integrations_firehydrant.py b/tests/test_integrations_firehydrant.py
new file mode 100644
index 0000000..3597a57
--- /dev/null
+++ b/tests/test_integrations_firehydrant.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_firehydrant_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_firehydrant_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/firehydrant/configuration", json={}, status=200)
+ cli(["integrations", "firehydrant", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_firehydrant_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/firehydrant/default-configuration", json={}, status=200)
+ cli(["integrations", "firehydrant", "get"])
+
+@responses.activate
+def test_integrations_firehydrant_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/firehydrant/configuration", json={}, status=200)
+ cli(["integrations", "firehydrant", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_firehydrant_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/firehydrant/configuration/validate", json={}, status=200)
+ cli(["integrations", "firehydrant", "validate"])
+
+@responses.activate
+def test_integrations_firehydrant_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/firehydrant/configurations", json={}, status=200)
+ cli(["integrations", "firehydrant", "delete"])
diff --git a/tests/test_integrations_instana.py b/tests/test_integrations_instana.py
new file mode 100644
index 0000000..d03471d
--- /dev/null
+++ b/tests/test_integrations_instana.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_instana_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_instana_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/instana/configuration", json={}, status=200)
+ cli(["integrations", "instana", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_instana_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/instana/default-configuration", json={}, status=200)
+ cli(["integrations", "instana", "get"])
+
+@responses.activate
+def test_integrations_instana_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/instana/configuration", json={}, status=200)
+ cli(["integrations", "instana", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_instana_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/instana/configuration/validate", json={}, status=200)
+ cli(["integrations", "instana", "validate"])
+
+@responses.activate
+def test_integrations_instana_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/instana/configurations", json={}, status=200)
+ cli(["integrations", "instana", "delete"])
diff --git a/tests/test_integrations_jenkins.py b/tests/test_integrations_jenkins.py
new file mode 100644
index 0000000..dd92cad
--- /dev/null
+++ b/tests/test_integrations_jenkins.py
@@ -0,0 +1,59 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_jenkins_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_jenkins_add():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/jenkins/configuration", json={}, status=200)
+ cli(["integrations", "jenkins", "add", "-a", "myAlias", "--api-key", "123456", "-h", "https://jenkins.example.com", "-u", "admin", "-i"])
+
+@responses.activate
+def test_integrations_jenkins_add_multiple(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/jenkins/configurations", json={}, status=200)
+ cli(["integrations", "jenkins", "add-multiple", "-f", str(f)])
+
+@responses.activate
+def test_integrations_jenkins_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/jenkins/configuration/test", status=200)
+ cli(["integrations", "jenkins", "delete", "-a", "test"])
+
+@responses.activate
+def test_integrations_jenkins_delete_all():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/jenkins/configurations", status=200)
+ cli(["integrations", "jenkins", "delete-all"])
+
+@responses.activate
+def test_integrations_jenkins_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/jenkins/configuration/test", json={}, status=200)
+ cli(["integrations", "jenkins", "get", "-a", "test"])
+
+@responses.activate
+def test_integrations_jenkins_list():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/jenkins/configurations", json={}, status=200)
+ cli(["integrations", "jenkins", "list"])
+
+@responses.activate
+def test_integrations_jenkins_get_default():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/jenkins/default-configuration", json={}, status=200)
+ cli(["integrations", "jenkins", "get-default"])
+
+@responses.activate
+def test_integrations_jenkins_update():
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/jenkins/configuration/test", json={}, status=200)
+ cli(["integrations", "jenkins", "update", "-a", "test", "-i"])
+
+@responses.activate
+def test_integrations_jenkins_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/jenkins/configuration/validate/test", json={}, status=200)
+ cli(["integrations", "jenkins", "validate", "-a", "test"])
+
+@responses.activate
+def test_integrations_jenkins_validate_all():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/jenkins/configuration/validate", json={}, status=200)
+ cli(["integrations", "jenkins", "validate-all"])
diff --git a/tests/test_integrations_jira.py b/tests/test_integrations_jira.py
new file mode 100644
index 0000000..fd10595
--- /dev/null
+++ b/tests/test_integrations_jira.py
@@ -0,0 +1,60 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_jira_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_jira_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/jira/configuration", json={}, status=200)
+ cli(["integrations", "jira", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_jira_add_multiple(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/jira/configurations", json={}, status=200)
+ cli(["integrations", "jira", "add-multiple", "-f", str(f)])
+
+@responses.activate
+def test_integrations_jira_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/jira/configuration/test", status=200)
+ cli(["integrations", "jira", "delete", "-a", "test"])
+
+@responses.activate
+def test_integrations_jira_delete_all():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/jira/configurations", status=200)
+ cli(["integrations", "jira", "delete-all"])
+
+@responses.activate
+def test_integrations_jira_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/jira/configuration/test", json={}, status=200)
+ cli(["integrations", "jira", "get", "-a", "test"])
+
+@responses.activate
+def test_integrations_jira_list():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/jira/configurations", json={}, status=200)
+ cli(["integrations", "jira", "list"])
+
+@responses.activate
+def test_integrations_jira_get_default():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/jira/default-configuration", json={}, status=200)
+ cli(["integrations", "jira", "get-default"])
+
+@responses.activate
+def test_integrations_jira_update():
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/jira/configuration/test", json={}, status=200)
+ cli(["integrations", "jira", "update", "-a", "test", "-i"])
+
+@responses.activate
+def test_integrations_jira_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/jira/configuration/validate/test", json={}, status=200)
+ cli(["integrations", "jira", "validate", "-a", "test"])
+
+@responses.activate
+def test_integrations_jira_validate_all():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/jira/configuration/validate", json={}, status=200)
+ cli(["integrations", "jira", "validate-all"])
diff --git a/tests/test_integrations_mend_sast.py b/tests/test_integrations_mend_sast.py
new file mode 100644
index 0000000..0087ea8
--- /dev/null
+++ b/tests/test_integrations_mend_sast.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_mend_sast_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_mend_sast_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/mend/sast/configuration", json={}, status=200)
+ cli(["integrations", "mend-sast", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_mend_sast_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/mend/sast/default-configuration", json={}, status=200)
+ cli(["integrations", "mend-sast", "get"])
+
+@responses.activate
+def test_integrations_mend_sast_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/mend/sast/configuration", json={}, status=200)
+ cli(["integrations", "mend-sast", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_mend_sast_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/mend/sast/configuration/validate", json={}, status=200)
+ cli(["integrations", "mend-sast", "validate"])
+
+@responses.activate
+def test_integrations_mend_sast_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/mend/sast/configurations", json={}, status=200)
+ cli(["integrations", "mend-sast", "delete"])
diff --git a/tests/test_integrations_mend_sca.py b/tests/test_integrations_mend_sca.py
new file mode 100644
index 0000000..44f1e0e
--- /dev/null
+++ b/tests/test_integrations_mend_sca.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_mend_sca_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_mend_sca_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/mend/sca/configuration", json={}, status=200)
+ cli(["integrations", "mend-sca", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_mend_sca_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/mend/sca/default-configuration", json={}, status=200)
+ cli(["integrations", "mend-sca", "get"])
+
+@responses.activate
+def test_integrations_mend_sca_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/mend/sca/configuration", json={}, status=200)
+ cli(["integrations", "mend-sca", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_mend_sca_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/mend/sca/configuration/validate", json={}, status=200)
+ cli(["integrations", "mend-sca", "validate"])
+
+@responses.activate
+def test_integrations_mend_sca_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/mend/sca/configurations", json={}, status=200)
+ cli(["integrations", "mend-sca", "delete"])
diff --git a/tests/test_integrations_okta.py b/tests/test_integrations_okta.py
new file mode 100644
index 0000000..2633233
--- /dev/null
+++ b/tests/test_integrations_okta.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_okta_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_okta_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/okta/configuration", json={}, status=200)
+ cli(["integrations", "okta", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_okta_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/okta/default-configuration", json={}, status=200)
+ cli(["integrations", "okta", "get"])
+
+@responses.activate
+def test_integrations_okta_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/okta/configuration", json={}, status=200)
+ cli(["integrations", "okta", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_okta_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/okta/configuration/validate", json={}, status=200)
+ cli(["integrations", "okta", "validate"])
+
+@responses.activate
+def test_integrations_okta_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/okta/configurations", json={}, status=200)
+ cli(["integrations", "okta", "delete"])
diff --git a/tests/test_integrations_opsgenie.py b/tests/test_integrations_opsgenie.py
new file mode 100644
index 0000000..9165c50
--- /dev/null
+++ b/tests/test_integrations_opsgenie.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_opsgenie_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_opsgenie_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/opsgenie/configuration", json={}, status=200)
+ cli(["integrations", "opsgenie", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_opsgenie_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/opsgenie/default-configuration", json={}, status=200)
+ cli(["integrations", "opsgenie", "get"])
+
+@responses.activate
+def test_integrations_opsgenie_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/opsgenie/configuration", json={}, status=200)
+ cli(["integrations", "opsgenie", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_opsgenie_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/opsgenie/configuration/validate", json={}, status=200)
+ cli(["integrations", "opsgenie", "validate"])
+
+@responses.activate
+def test_integrations_opsgenie_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/opsgenie/configurations", json={}, status=200)
+ cli(["integrations", "opsgenie", "delete"])
diff --git a/tests/test_integrations_rollbar.py b/tests/test_integrations_rollbar.py
new file mode 100644
index 0000000..9e06ac6
--- /dev/null
+++ b/tests/test_integrations_rollbar.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_rollbar_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_rollbar_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/rollbar/configuration", json={}, status=200)
+ cli(["integrations", "rollbar", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_rollbar_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/rollbar/default-configuration", json={}, status=200)
+ cli(["integrations", "rollbar", "get"])
+
+@responses.activate
+def test_integrations_rollbar_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/rollbar/configuration", json={}, status=200)
+ cli(["integrations", "rollbar", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_rollbar_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/rollbar/configuration/validate", json={}, status=200)
+ cli(["integrations", "rollbar", "validate"])
+
+@responses.activate
+def test_integrations_rollbar_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/rollbar/configurations", json={}, status=200)
+ cli(["integrations", "rollbar", "delete"])
diff --git a/tests/test_integrations_rootly.py b/tests/test_integrations_rootly.py
new file mode 100644
index 0000000..5cf8f5a
--- /dev/null
+++ b/tests/test_integrations_rootly.py
@@ -0,0 +1,59 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_rootly_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_rootly_add():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/rootly/configuration", json={}, status=200)
+ cli(["integrations", "rootly", "add", "-a", "myAlias", "--api-key", "123456", "-i"])
+
+@responses.activate
+def test_integrations_rootly_add_multiple(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/rootly/configurations", json={}, status=200)
+ cli(["integrations", "rootly", "add-multiple", "-f", str(f)])
+
+@responses.activate
+def test_integrations_rootly_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/rootly/configuration/test", status=200)
+ cli(["integrations", "rootly", "delete", "-a", "test"])
+
+@responses.activate
+def test_integrations_rootly_delete_all():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/rootly/configurations", status=200)
+ cli(["integrations", "rootly", "delete-all"])
+
+@responses.activate
+def test_integrations_rootly_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/rootly/configuration/test", json={}, status=200)
+ cli(["integrations", "rootly", "get", "-a", "test"])
+
+@responses.activate
+def test_integrations_rootly_list():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/rootly/configurations", json={}, status=200)
+ cli(["integrations", "rootly", "list"])
+
+@responses.activate
+def test_integrations_rootly_get_default():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/rootly/default-configuration", json={}, status=200)
+ cli(["integrations", "rootly", "get-default"])
+
+@responses.activate
+def test_integrations_rootly_update():
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/rootly/configuration/test", json={}, status=200)
+ cli(["integrations", "rootly", "update", "-a", "test", "-i"])
+
+@responses.activate
+def test_integrations_rootly_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/rootly/configuration/validate/test", json={}, status=200)
+ cli(["integrations", "rootly", "validate", "-a", "test"])
+
+@responses.activate
+def test_integrations_rootly_validate_all():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/rootly/configuration/validate", json={}, status=200)
+ cli(["integrations", "rootly", "validate-all"])
diff --git a/tests/test_integrations_semgrep.py b/tests/test_integrations_semgrep.py
new file mode 100644
index 0000000..bd49158
--- /dev/null
+++ b/tests/test_integrations_semgrep.py
@@ -0,0 +1,59 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_semgrep_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_semgrep_add():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/semgrep/configuration", json={}, status=200)
+ cli(["integrations", "semgrep", "add", "-a", "myAlias", "--api-key", "123456", "-oi", "org123", "-os", "my-org", "-i"])
+
+@responses.activate
+def test_integrations_semgrep_add_multiple(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/semgrep/configurations", json={}, status=200)
+ cli(["integrations", "semgrep", "add-multiple", "-f", str(f)])
+
+@responses.activate
+def test_integrations_semgrep_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/semgrep/configuration/test", status=200)
+ cli(["integrations", "semgrep", "delete", "-a", "test"])
+
+@responses.activate
+def test_integrations_semgrep_delete_all():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/semgrep/configurations", status=200)
+ cli(["integrations", "semgrep", "delete-all"])
+
+@responses.activate
+def test_integrations_semgrep_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/semgrep/configuration/test", json={}, status=200)
+ cli(["integrations", "semgrep", "get", "-a", "test"])
+
+@responses.activate
+def test_integrations_semgrep_list():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/semgrep/configurations", json={}, status=200)
+ cli(["integrations", "semgrep", "list"])
+
+@responses.activate
+def test_integrations_semgrep_get_default():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/semgrep/default-configuration", json={}, status=200)
+ cli(["integrations", "semgrep", "get-default"])
+
+@responses.activate
+def test_integrations_semgrep_update():
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/semgrep/configuration/test", json={}, status=200)
+ cli(["integrations", "semgrep", "update", "-a", "test", "-i"])
+
+@responses.activate
+def test_integrations_semgrep_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/semgrep/configuration/validate/test", json={}, status=200)
+ cli(["integrations", "semgrep", "validate", "-a", "test"])
+
+@responses.activate
+def test_integrations_semgrep_validate_all():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/semgrep/configuration/validate", json={}, status=200)
+ cli(["integrations", "semgrep", "validate-all"])
diff --git a/tests/test_integrations_sentry.py b/tests/test_integrations_sentry.py
new file mode 100644
index 0000000..d9e0fb1
--- /dev/null
+++ b/tests/test_integrations_sentry.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_sentry_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_sentry_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/sentry/configuration", json={}, status=200)
+ cli(["integrations", "sentry", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_sentry_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/sentry/default-configuration", json={}, status=200)
+ cli(["integrations", "sentry", "get"])
+
+@responses.activate
+def test_integrations_sentry_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/sentry/configuration", json={}, status=200)
+ cli(["integrations", "sentry", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_sentry_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/sentry/configuration/validate", json={}, status=200)
+ cli(["integrations", "sentry", "validate"])
+
+@responses.activate
+def test_integrations_sentry_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/sentry/configurations", json={}, status=200)
+ cli(["integrations", "sentry", "delete"])
diff --git a/tests/test_integrations_servicenow.py b/tests/test_integrations_servicenow.py
new file mode 100644
index 0000000..bf94756
--- /dev/null
+++ b/tests/test_integrations_servicenow.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_servicenow_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_servicenow_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/servicenow/configuration", json={}, status=200)
+ cli(["integrations", "servicenow", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_servicenow_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/servicenow/default-configuration", json={}, status=200)
+ cli(["integrations", "servicenow", "get"])
+
+@responses.activate
+def test_integrations_servicenow_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/servicenow/configuration", json={}, status=200)
+ cli(["integrations", "servicenow", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_servicenow_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/servicenow/configuration/validate", json={}, status=200)
+ cli(["integrations", "servicenow", "validate"])
+
+@responses.activate
+def test_integrations_servicenow_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/servicenow/configurations", json={}, status=200)
+ cli(["integrations", "servicenow", "delete"])
diff --git a/tests/test_integrations_servicenow_cloud_observability.py b/tests/test_integrations_servicenow_cloud_observability.py
new file mode 100644
index 0000000..59f9b1b
--- /dev/null
+++ b/tests/test_integrations_servicenow_cloud_observability.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_servicenow_cloud_observability_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_servicenow_cloud_observability_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/lightstep/configuration", json={}, status=200)
+ cli(["integrations", "servicenow-cloud-observability", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_servicenow_cloud_observability_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/lightstep/default-configuration", json={}, status=200)
+ cli(["integrations", "servicenow-cloud-observability", "get"])
+
+@responses.activate
+def test_integrations_servicenow_cloud_observability_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/lightstep/configuration", json={}, status=200)
+ cli(["integrations", "servicenow-cloud-observability", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_servicenow_cloud_observability_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/lightstep/configuration/validate", json={}, status=200)
+ cli(["integrations", "servicenow-cloud-observability", "validate"])
+
+@responses.activate
+def test_integrations_servicenow_cloud_observability_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/lightstep/configurations", json={}, status=200)
+ cli(["integrations", "servicenow-cloud-observability", "delete"])
diff --git a/tests/test_integrations_snyk.py b/tests/test_integrations_snyk.py
new file mode 100644
index 0000000..9eeff7d
--- /dev/null
+++ b/tests/test_integrations_snyk.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_snyk_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_snyk_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/snyk/configuration", json={}, status=200)
+ cli(["integrations", "snyk", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_snyk_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/snyk/default-configuration", json={}, status=200)
+ cli(["integrations", "snyk", "get"])
+
+@responses.activate
+def test_integrations_snyk_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/snyk/configuration", json={}, status=200)
+ cli(["integrations", "snyk", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_snyk_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/snyk/configuration/validate", json={}, status=200)
+ cli(["integrations", "snyk", "validate"])
+
+@responses.activate
+def test_integrations_snyk_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/snyk/configurations", json={}, status=200)
+ cli(["integrations", "snyk", "delete"])
diff --git a/tests/test_integrations_splunk_observability_cloud.py b/tests/test_integrations_splunk_observability_cloud.py
new file mode 100644
index 0000000..8260a72
--- /dev/null
+++ b/tests/test_integrations_splunk_observability_cloud.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_splunk_observability_cloud_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_splunk_observability_cloud_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/signalfx/configuration", json={}, status=200)
+ cli(["integrations", "splunk-observability-cloud", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_splunk_observability_cloud_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/signalfx/default-configuration", json={}, status=200)
+ cli(["integrations", "splunk-observability-cloud", "get"])
+
+@responses.activate
+def test_integrations_splunk_observability_cloud_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/signalfx/configuration", json={}, status=200)
+ cli(["integrations", "splunk-observability-cloud", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_splunk_observability_cloud_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/signalfx/configuration/validate", json={}, status=200)
+ cli(["integrations", "splunk-observability-cloud", "validate"])
+
+@responses.activate
+def test_integrations_splunk_observability_cloud_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/signalfx/configurations", json={}, status=200)
+ cli(["integrations", "splunk-observability-cloud", "delete"])
diff --git a/tests/test_integrations_splunk_on_call.py b/tests/test_integrations_splunk_on_call.py
new file mode 100644
index 0000000..2a421c1
--- /dev/null
+++ b/tests/test_integrations_splunk_on_call.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_splunk_on_call_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_splunk_on_call_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/victorops/configuration", json={}, status=200)
+ cli(["integrations", "splunk-on-call", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_splunk_on_call_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/victorops/default-configuration", json={}, status=200)
+ cli(["integrations", "splunk-on-call", "get"])
+
+@responses.activate
+def test_integrations_splunk_on_call_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/victorops/configuration", json={}, status=200)
+ cli(["integrations", "splunk-on-call", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_splunk_on_call_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/victorops/configuration/validate", json={}, status=200)
+ cli(["integrations", "splunk-on-call", "validate"])
+
+@responses.activate
+def test_integrations_splunk_on_call_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/victorops/configurations", json={}, status=200)
+ cli(["integrations", "splunk-on-call", "delete"])
diff --git a/tests/test_integrations_sumologic.py b/tests/test_integrations_sumologic.py
new file mode 100644
index 0000000..2f7db9c
--- /dev/null
+++ b/tests/test_integrations_sumologic.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_sumologic_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_sumologic_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/sumologic/configuration", json={}, status=200)
+ cli(["integrations", "sumo-logic", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_sumologic_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/sumologic/default-configuration", json={}, status=200)
+ cli(["integrations", "sumo-logic", "get"])
+
+@responses.activate
+def test_integrations_sumologic_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/sumologic/configuration", json={}, status=200)
+ cli(["integrations", "sumo-logic", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_sumologic_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/sumologic/configuration/validate", json={}, status=200)
+ cli(["integrations", "sumo-logic", "validate"])
+
+@responses.activate
+def test_integrations_sumologic_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/sumologic/configurations", json={}, status=200)
+ cli(["integrations", "sumo-logic", "delete"])
diff --git a/tests/test_integrations_veracode.py b/tests/test_integrations_veracode.py
new file mode 100644
index 0000000..8b5a731
--- /dev/null
+++ b/tests/test_integrations_veracode.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_veracode_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_veracode_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/veracode/configuration", json={}, status=200)
+ cli(["integrations", "veracode", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_veracode_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/veracode/default-configuration", json={}, status=200)
+ cli(["integrations", "veracode", "get"])
+
+@responses.activate
+def test_integrations_veracode_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/veracode/configuration", json={}, status=200)
+ cli(["integrations", "veracode", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_veracode_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/veracode/configuration/validate", json={}, status=200)
+ cli(["integrations", "veracode", "validate"])
+
+@responses.activate
+def test_integrations_veracode_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/veracode/configurations", json={}, status=200)
+ cli(["integrations", "veracode", "delete"])
diff --git a/tests/test_integrations_wiz.py b/tests/test_integrations_wiz.py
new file mode 100644
index 0000000..886568b
--- /dev/null
+++ b/tests/test_integrations_wiz.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_wiz_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_wiz_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/wiz/configuration", json={}, status=200)
+ cli(["integrations", "wiz", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_wiz_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/wiz/default-configuration", json={}, status=200)
+ cli(["integrations", "wiz", "get"])
+
+@responses.activate
+def test_integrations_wiz_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/wiz/configuration", json={}, status=200)
+ cli(["integrations", "wiz", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_wiz_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/wiz/configuration/validate", json={}, status=200)
+ cli(["integrations", "wiz", "validate"])
+
+@responses.activate
+def test_integrations_wiz_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/wiz/configurations", json={}, status=200)
+ cli(["integrations", "wiz", "delete"])
diff --git a/tests/test_integrations_workday.py b/tests/test_integrations_workday.py
new file mode 100644
index 0000000..c9957ee
--- /dev/null
+++ b/tests/test_integrations_workday.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_workday_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_workday_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/workday/configuration", json={}, status=200)
+ cli(["integrations", "workday", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_workday_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/workday/default-configuration", json={}, status=200)
+ cli(["integrations", "workday", "get"])
+
+@responses.activate
+def test_integrations_workday_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/workday/configuration", json={}, status=200)
+ cli(["integrations", "workday", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_workday_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/workday/configuration/validate", json={}, status=200)
+ cli(["integrations", "workday", "validate"])
+
+@responses.activate
+def test_integrations_workday_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/workday/configurations", json={}, status=200)
+ cli(["integrations", "workday", "delete"])
diff --git a/tests/test_integrations_xmatters.py b/tests/test_integrations_xmatters.py
new file mode 100644
index 0000000..12e23a5
--- /dev/null
+++ b/tests/test_integrations_xmatters.py
@@ -0,0 +1,35 @@
+from tests.helpers.utils import *
+
+# Since responses are all mocked and no data validation is done by the CLI --
+# we let the API handle validation -- we don't need valid input files.
+def _dummy_file(tmp_path):
+ f = tmp_path / "test_integrations_xmatters_add.json"
+ f.write_text("foobar")
+ return f
+
+@responses.activate
+def test_integrations_xmatters_add(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/xmatters/configuration", json={}, status=200)
+ cli(["integrations", "xmatters", "add", "-f", str(f)])
+
+@responses.activate
+def test_integrations_xmatters_get():
+ responses.add(responses.GET, os.getenv("CORTEX_BASE_URL") + "/api/v1/xmatters/default-configuration", json={}, status=200)
+ cli(["integrations", "xmatters", "get"])
+
+@responses.activate
+def test_integrations_xmatters_update(tmp_path):
+ f = _dummy_file(tmp_path)
+ responses.add(responses.PUT, os.getenv("CORTEX_BASE_URL") + "/api/v1/xmatters/configuration", json={}, status=200)
+ cli(["integrations", "xmatters", "update", "-f", str(f)])
+
+@responses.activate
+def test_integrations_xmatters_validate():
+ responses.add(responses.POST, os.getenv("CORTEX_BASE_URL") + "/api/v1/xmatters/configuration/validate", json={}, status=200)
+ cli(["integrations", "xmatters", "validate"])
+
+@responses.activate
+def test_integrations_xmatters_delete():
+ responses.add(responses.DELETE, os.getenv("CORTEX_BASE_URL") + "/api/v1/xmatters/configurations", json={}, status=200)
+ cli(["integrations", "xmatters", "delete"])