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"])