cortexapps-cli 1.30.0__tar.gz → 1.32.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/PKG-INFO +2 -1
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations.py +2 -0
- cortexapps_cli-1.32.0/cortexapps_cli/commands/integrations_commands/harness.py +160 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/solutions.py +114 -17
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/_lib/__init__.py +0 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/_lib/setup_base.py +183 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/README.md +97 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/_templates/cortex-async-callback.yml +39 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/_templates/cortex-deploy.yml +51 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/_templates/cortex-register-deploy.yml +51 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/_templates/github-actions-deploy.yaml +132 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/catalog/github-actions-demo.yaml +9 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/scorecards/deploy-health.yaml +51 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/setup.py +508 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/README.md +112 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/_templates/cortex-async-callback-template.yaml +59 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/_templates/cortex-deploy-pipeline.yaml +110 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/_templates/cortex-record-deploy-template.yaml +55 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/_templates/trigger-harness-deploy.yaml +177 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/catalog/harness-demo.yaml +14 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/scorecards/deploy-health.yaml +51 -0
- cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/setup.py +520 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/pyproject.toml +2 -1
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/LICENSE +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/README.rst +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/__init__.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/cli.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/command_options.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/ai_skills.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/api_keys.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/audit_logs.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/backup.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/backup_commands/cortex_export.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/catalog.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/custom_data.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/custom_events.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/custom_metrics.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/dependencies.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/deploys.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/discovery_audit.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/docs.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/entity_relationship_types.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/entity_relationships.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/entity_types.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/gitops_logs.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/groups.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/initiatives.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/apiiro.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/argocd.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/aws.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/azure_active_directory.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/azure_devops.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/azure_resources.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/bamboohr.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/bitbucket.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/bugsnag.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/buildkite.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/checkmarx_sast.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/circleci.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/clickup.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/codecov.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/coralogix.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/datadog.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/dynatrace.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/firehydrant.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/github.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/gitlab.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/incidentio.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/instana.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/jenkins.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/jira.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/launchdarkly.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/mend_sast.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/mend_sca.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/newrelic.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/okta.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/opsgenie.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/pagerduty.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/prometheus.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/rollbar.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/rootly.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/semgrep.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/sentry.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/servicenow.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/servicenow_cloud_observability.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/snyk.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/sonarqube.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/splunk_observability_cloud.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/splunk_on_call.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/sumologic.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/veracode.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/wiz.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/workday.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/xmatters.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/ip_allowlist.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/on_call.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages_commands/go.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages_commands/java.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages_commands/node.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages_commands/nuget.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages_commands/python.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/plugins.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/queries.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/rest.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/scim.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/scorecards.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/scorecards_commands/exemptions.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/secrets.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/teams.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/users.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/users_commands/roles.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/workflows.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/cortex_client.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/models/team.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/__init__.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/README.md +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/address-reviews.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/audit-flags.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/code-review.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/fix-issue.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/incident-trends.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/jira-to-kb.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/release-to-sha.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/search-troubleshooting-kb.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/simplify-test.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/entity-types/ai-agent.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/scorecards/ai-agent-governance.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/README.md +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/catalog/api-doc-sync.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/catalog/changelog-writer.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/catalog/docs-generator.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/catalog/docs-portal.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/entity-relationship-types/ai-plugin-service.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/entity-relationship-types/ai-plugin-skills.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/entity-relationship-types/ai-skill-service.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/entity-types/ai-plugin.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/entity-types/ai-skill.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/scorecards/ai-skills-quality.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/.github/workflows/sync-claude-spend.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/README.md +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-alice-chen.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-bob-martinez.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-carol-kim.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-david-osei.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-emma-johnson.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/team-data.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/team-engineering.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/team-frontend.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/team-platform.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/custom-metrics/ai-spend.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/entity-relationship-types/team-member.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/entity-types/employee.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/plugins/team-ai-spend.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/scorecards/ai-spend-scorecard.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/scripts/sync-claude-spend.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/.github/workflows/cortex-service-version.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/README.md +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/api-gateway-3.0.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/api-gateway-3.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/api-gateway-3.1.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/api-gateway.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/auth-2.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/auth-2.1.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/auth-2.2.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/auth.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/aws-dev-us-west-2.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/billing-1.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/billing-1.2.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/billing.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/cart-1.0.2.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/cart.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/checkout-2.2.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/checkout-2.3.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/checkout-2.4.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/checkout.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/gcp-prod-us-east-1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/gcp-prod-us-west-1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/gcp-staging-us-east-1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/notifications-1.0.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/notifications-1.0.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/notifications-1.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/notifications.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/order-service-2.0.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/order-service-2.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/order-service.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/payments-1.6.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/payments-1.7.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/payments-1.7.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/payments.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/release-2026-06-20.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/release-2026-07-01.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/release-2026-07-09.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/release-2026-07-10.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/search-4.0.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/search-4.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/search.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/user-service-1.2.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/user-service-1.3.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/user-service.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/entity-relationship-types/environments.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/entity-relationship-types/versions.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/entity-types/environment.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/entity-types/release.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/entity-types/service-version.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/workflows/create-environment.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/workflows/create-release.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/workflows/trigger-github-release.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cortexapps-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.32.0
|
|
4
4
|
Summary: Command Line Interface for cortexapps
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: PyNaCl (>=1.5.0)
|
|
17
18
|
Requires-Dist: pyyaml (>=6.0.1,<7)
|
|
18
19
|
Requires-Dist: requests (>=2.33.0,<3.0.0)
|
|
19
20
|
Requires-Dist: typer (>=0.15,<1.0)
|
|
@@ -23,6 +23,7 @@ import cortexapps_cli.commands.integrations_commands.dynatrace as dynatrace
|
|
|
23
23
|
import cortexapps_cli.commands.integrations_commands.firehydrant as firehydrant
|
|
24
24
|
import cortexapps_cli.commands.integrations_commands.github as github
|
|
25
25
|
import cortexapps_cli.commands.integrations_commands.gitlab as gitlab
|
|
26
|
+
import cortexapps_cli.commands.integrations_commands.harness as harness
|
|
26
27
|
import cortexapps_cli.commands.integrations_commands.incidentio as incidentio
|
|
27
28
|
import cortexapps_cli.commands.integrations_commands.instana as instana
|
|
28
29
|
import cortexapps_cli.commands.integrations_commands.jenkins as jenkins
|
|
@@ -72,6 +73,7 @@ app.add_typer(dynatrace.app, name="dynatrace")
|
|
|
72
73
|
app.add_typer(firehydrant.app, name="firehydrant")
|
|
73
74
|
app.add_typer(github.app, name="github")
|
|
74
75
|
app.add_typer(gitlab.app, name="gitlab")
|
|
76
|
+
app.add_typer(harness.app, name="harness")
|
|
75
77
|
app.add_typer(incidentio.app, name="incidentio")
|
|
76
78
|
app.add_typer(instana.app, name="instana")
|
|
77
79
|
app.add_typer(jenkins.app, name="jenkins")
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from rich import print_json
|
|
3
|
+
import typer
|
|
4
|
+
from typing_extensions import Annotated
|
|
5
|
+
|
|
6
|
+
app = typer.Typer(help="Harness commands", no_args_is_help=True)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.command()
|
|
10
|
+
def add(
|
|
11
|
+
ctx: typer.Context,
|
|
12
|
+
alias: str = typer.Option(..., "--alias", "-a", help="Alias for this configuration"),
|
|
13
|
+
api_key: str = typer.Option(..., "--api-key", "-k", help="Harness API key"),
|
|
14
|
+
account_id: str = typer.Option(..., "--account-id", "-id", help="Harness account ID"),
|
|
15
|
+
host: str = typer.Option(None, "--host", "-h", help="Harness host URL (optional; defaults to https://app.harness.io)"),
|
|
16
|
+
is_default: bool = typer.Option(False, "--is-default", "-i", help="Set as the default configuration"),
|
|
17
|
+
file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration; use - for stdin")] = None,
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Add a Harness configuration
|
|
21
|
+
"""
|
|
22
|
+
client = ctx.obj["client"]
|
|
23
|
+
|
|
24
|
+
if file_input:
|
|
25
|
+
if alias or api_key or account_id or host or is_default:
|
|
26
|
+
raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
|
|
27
|
+
data = json.loads("".join([line for line in file_input]))
|
|
28
|
+
else:
|
|
29
|
+
data = {
|
|
30
|
+
"alias": alias,
|
|
31
|
+
"apiKey": api_key,
|
|
32
|
+
"accountId": account_id,
|
|
33
|
+
"isDefault": is_default,
|
|
34
|
+
}
|
|
35
|
+
if host is not None:
|
|
36
|
+
data["host"] = host
|
|
37
|
+
|
|
38
|
+
r = client.post("api/v1/harness/configuration", data=data)
|
|
39
|
+
print_json(data=r)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command()
|
|
43
|
+
def get(
|
|
44
|
+
ctx: typer.Context,
|
|
45
|
+
alias: str = typer.Option(..., "--alias", "-a", help="Alias of the configuration to retrieve"),
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Get a single Harness configuration by alias
|
|
49
|
+
"""
|
|
50
|
+
client = ctx.obj["client"]
|
|
51
|
+
r = client.get(f"api/v1/harness/configuration/{alias}")
|
|
52
|
+
print_json(data=r)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@app.command()
|
|
56
|
+
def list(
|
|
57
|
+
ctx: typer.Context,
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
List all Harness configurations
|
|
61
|
+
"""
|
|
62
|
+
client = ctx.obj["client"]
|
|
63
|
+
r = client.get("api/v1/harness/configurations")
|
|
64
|
+
print_json(data=r)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.command()
|
|
68
|
+
def get_default(
|
|
69
|
+
ctx: typer.Context,
|
|
70
|
+
):
|
|
71
|
+
"""
|
|
72
|
+
Get the default Harness configuration
|
|
73
|
+
"""
|
|
74
|
+
client = ctx.obj["client"]
|
|
75
|
+
r = client.get("api/v1/harness/default-configuration")
|
|
76
|
+
print_json(data=r)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.command()
|
|
80
|
+
def update(
|
|
81
|
+
ctx: typer.Context,
|
|
82
|
+
alias: str = typer.Option(..., "--alias", "-a", help="Alias of the configuration to update"),
|
|
83
|
+
api_key: str = typer.Option(None, "--api-key", "-k", help="New Harness API key"),
|
|
84
|
+
account_id: str = typer.Option(None, "--account-id", "-id", help="New Harness account ID"),
|
|
85
|
+
host: str = typer.Option(None, "--host", "-h", help="New Harness host URL"),
|
|
86
|
+
is_default: bool = typer.Option(None, "--is-default", "-i", help="Set as the default configuration"),
|
|
87
|
+
file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing update fields; use - for stdin")] = None,
|
|
88
|
+
):
|
|
89
|
+
"""
|
|
90
|
+
Update a Harness configuration
|
|
91
|
+
"""
|
|
92
|
+
client = ctx.obj["client"]
|
|
93
|
+
|
|
94
|
+
if file_input:
|
|
95
|
+
if alias or api_key or account_id or host or is_default is not None:
|
|
96
|
+
raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
|
|
97
|
+
data = json.loads("".join([line for line in file_input]))
|
|
98
|
+
else:
|
|
99
|
+
data = {}
|
|
100
|
+
if api_key is not None:
|
|
101
|
+
data["apiKey"] = api_key
|
|
102
|
+
if account_id is not None:
|
|
103
|
+
data["accountId"] = account_id
|
|
104
|
+
if host is not None:
|
|
105
|
+
data["host"] = host
|
|
106
|
+
if is_default is not None:
|
|
107
|
+
data["isDefault"] = is_default
|
|
108
|
+
|
|
109
|
+
r = client.put(f"api/v1/harness/configuration/{alias}", data=data)
|
|
110
|
+
print_json(data=r)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@app.command()
|
|
114
|
+
def delete(
|
|
115
|
+
ctx: typer.Context,
|
|
116
|
+
alias: str = typer.Option(..., "--alias", "-a", help="Alias of the configuration to delete"),
|
|
117
|
+
):
|
|
118
|
+
"""
|
|
119
|
+
Delete a single Harness configuration
|
|
120
|
+
"""
|
|
121
|
+
client = ctx.obj["client"]
|
|
122
|
+
r = client.delete(f"api/v1/harness/configuration/{alias}")
|
|
123
|
+
print_json(data=r)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@app.command()
|
|
127
|
+
def delete_all(
|
|
128
|
+
ctx: typer.Context,
|
|
129
|
+
):
|
|
130
|
+
"""
|
|
131
|
+
Delete all Harness configurations
|
|
132
|
+
"""
|
|
133
|
+
client = ctx.obj["client"]
|
|
134
|
+
r = client.delete("api/v1/harness/configurations")
|
|
135
|
+
print_json(data=r)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@app.command()
|
|
139
|
+
def validate(
|
|
140
|
+
ctx: typer.Context,
|
|
141
|
+
alias: str = typer.Option(..., "--alias", "-a", help="Alias of the configuration to validate"),
|
|
142
|
+
):
|
|
143
|
+
"""
|
|
144
|
+
Validate a single Harness configuration
|
|
145
|
+
"""
|
|
146
|
+
client = ctx.obj["client"]
|
|
147
|
+
r = client.post(f"api/v1/harness/configuration/validate/{alias}")
|
|
148
|
+
print_json(data=r)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@app.command()
|
|
152
|
+
def validate_all(
|
|
153
|
+
ctx: typer.Context,
|
|
154
|
+
):
|
|
155
|
+
"""
|
|
156
|
+
Validate all Harness configurations
|
|
157
|
+
"""
|
|
158
|
+
client = ctx.obj["client"]
|
|
159
|
+
r = client.post("api/v1/harness/configuration/validate")
|
|
160
|
+
print_json(data=r)
|
|
@@ -74,13 +74,26 @@ class _ToggleableCapture:
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
def _run_import_with_toggle(fn) -> str:
|
|
77
|
-
"""Run fn() capturing stdout. On a TTY, Ctrl+o toggles live output.
|
|
77
|
+
"""Run fn() capturing stdout. On a TTY, Ctrl+o toggles live output.
|
|
78
|
+
|
|
79
|
+
If fn() raises for any reason, all buffered output is flushed to the
|
|
80
|
+
terminal before the exception propagates — no silent failures.
|
|
81
|
+
"""
|
|
78
82
|
real_stdout = sys.stdout
|
|
79
83
|
capture = _ToggleableCapture(real_stdout)
|
|
80
84
|
|
|
81
85
|
if not (_TTY_SUPPORT and sys.stdin.isatty()):
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
success = False
|
|
87
|
+
try:
|
|
88
|
+
with contextlib.redirect_stdout(capture):
|
|
89
|
+
fn()
|
|
90
|
+
success = True
|
|
91
|
+
finally:
|
|
92
|
+
if not success:
|
|
93
|
+
buffered = capture.getvalue()
|
|
94
|
+
if buffered:
|
|
95
|
+
real_stdout.write(buffered)
|
|
96
|
+
real_stdout.flush()
|
|
84
97
|
return capture.getvalue()
|
|
85
98
|
|
|
86
99
|
done = threading.Event()
|
|
@@ -112,9 +125,11 @@ def _run_import_with_toggle(fn) -> str:
|
|
|
112
125
|
|
|
113
126
|
t = threading.Thread(target=_listen, daemon=True)
|
|
114
127
|
t.start()
|
|
128
|
+
success = False
|
|
115
129
|
try:
|
|
116
130
|
with contextlib.redirect_stdout(capture):
|
|
117
131
|
fn()
|
|
132
|
+
success = True
|
|
118
133
|
finally:
|
|
119
134
|
done.set()
|
|
120
135
|
t.join(timeout=0.5)
|
|
@@ -122,6 +137,11 @@ def _run_import_with_toggle(fn) -> str:
|
|
|
122
137
|
_termios.tcsetattr(fd, _termios.TCSADRAIN, old_settings)
|
|
123
138
|
except Exception:
|
|
124
139
|
pass
|
|
140
|
+
if not success:
|
|
141
|
+
buffered = capture.getvalue()
|
|
142
|
+
if buffered:
|
|
143
|
+
real_stdout.write(buffered)
|
|
144
|
+
real_stdout.flush()
|
|
125
145
|
|
|
126
146
|
return capture.getvalue()
|
|
127
147
|
|
|
@@ -175,6 +195,53 @@ def _get_readme(tag: str, path: str | None = None) -> str | None:
|
|
|
175
195
|
return None
|
|
176
196
|
|
|
177
197
|
|
|
198
|
+
def _has_post_install(tag: str, path: str | None = None) -> bool:
|
|
199
|
+
"""Return True if this solution has a post-install setup.py."""
|
|
200
|
+
try:
|
|
201
|
+
(_solutions_root(path) / tag / "setup.py").read_bytes()
|
|
202
|
+
return True
|
|
203
|
+
except Exception:
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _load_setup_module(solution_tag: str, solutions_dir: str | None = None):
|
|
208
|
+
"""Load a solution's setup.py module. Returns the module or None if not found."""
|
|
209
|
+
import importlib.util
|
|
210
|
+
|
|
211
|
+
root = _solutions_root(solutions_dir)
|
|
212
|
+
try:
|
|
213
|
+
with as_file(root / solution_tag / "setup.py") as setup_path:
|
|
214
|
+
spec = importlib.util.spec_from_file_location(f"{solution_tag}_setup", setup_path)
|
|
215
|
+
module = importlib.util.module_from_spec(spec)
|
|
216
|
+
spec.loader.exec_module(module)
|
|
217
|
+
return module
|
|
218
|
+
except FileNotFoundError:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _get_setup_description(solution_tag: str, solutions_dir: str | None = None) -> str:
|
|
223
|
+
"""Return the SETUP_DESCRIPTION from a solution's setup.py, or a generic fallback."""
|
|
224
|
+
module = _load_setup_module(solution_tag, solutions_dir)
|
|
225
|
+
if module:
|
|
226
|
+
return getattr(module, "SETUP_DESCRIPTION", "This solution includes a post-install setup script.")
|
|
227
|
+
return "This solution includes a post-install setup script."
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _run_post_install_script(solution_tag: str, solutions_dir: str | None = None, ctx=None, no_prompt: bool = False) -> None:
|
|
231
|
+
"""Find and invoke the solution's setup.py main() function."""
|
|
232
|
+
module = _load_setup_module(solution_tag, solutions_dir)
|
|
233
|
+
if module is None:
|
|
234
|
+
typer.echo("No post-install setup available for this solution.")
|
|
235
|
+
return
|
|
236
|
+
kwargs = {}
|
|
237
|
+
if ctx and ctx.obj and "client" in ctx.obj:
|
|
238
|
+
client = ctx.obj["client"]
|
|
239
|
+
kwargs["cortex_api_key"] = client.api_key
|
|
240
|
+
kwargs["cortex_base_url"] = client.base_url
|
|
241
|
+
kwargs["no_prompt"] = no_prompt
|
|
242
|
+
module.main(**kwargs)
|
|
243
|
+
|
|
244
|
+
|
|
178
245
|
def _extract_first_codeblock(text: str) -> str | None:
|
|
179
246
|
"""Return content of the first fenced code block."""
|
|
180
247
|
m = re.search(r"```[^\n]*\n(.*?)```", text, re.DOTALL)
|
|
@@ -333,10 +400,10 @@ def _run_uninstall(client, path: Path, yes: bool) -> None:
|
|
|
333
400
|
total = sum(len(v) for v in resources.values())
|
|
334
401
|
|
|
335
402
|
if total == 0:
|
|
336
|
-
typer.echo("No
|
|
403
|
+
typer.echo("No entities found to remove.")
|
|
337
404
|
return
|
|
338
405
|
|
|
339
|
-
typer.echo("\nThis will remove the following
|
|
406
|
+
typer.echo("\nThis will remove the following entities:")
|
|
340
407
|
for kind in ("workflows", "scorecards", "plugins", "catalog", "entity-relationship-types", "entity-types"):
|
|
341
408
|
count = len(resources[kind])
|
|
342
409
|
if count:
|
|
@@ -538,17 +605,6 @@ def _show_next_steps(readme: str) -> None:
|
|
|
538
605
|
section = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'\1 (\2)', section)
|
|
539
606
|
console.print()
|
|
540
607
|
console.print(Markdown(section))
|
|
541
|
-
console.print()
|
|
542
|
-
console.print(
|
|
543
|
-
"[magenta]Planned for Q4 2027:[/magenta] CQL metadata traversal will enable scorecard rules "
|
|
544
|
-
"across relationship chains — for example, a Vulnerability Scorecard checking that no "
|
|
545
|
-
"deployed service-version has open Snyk issues:"
|
|
546
|
-
)
|
|
547
|
-
console.print(
|
|
548
|
-
" [dim]entity.destinations(relationshipType = \"environments\", depth = 3)\n"
|
|
549
|
-
" .filter((d) => d.type == \"service-version\")\n"
|
|
550
|
-
" .all((sv) => sv.snyk.issues == 0)[/dim]"
|
|
551
|
-
)
|
|
552
608
|
|
|
553
609
|
|
|
554
610
|
def _post_install_menu(
|
|
@@ -604,6 +660,12 @@ def install(
|
|
|
604
660
|
ctx: typer.Context,
|
|
605
661
|
solution: str = typer.Option(..., "--solution", "-s", help="Solution tag"),
|
|
606
662
|
no_prompt: bool = typer.Option(False, "--no-prompt", help="Skip the post-install interactive menu"),
|
|
663
|
+
skip_post_install_setup: bool = typer.Option(
|
|
664
|
+
False,
|
|
665
|
+
"--skip-post-install-setup",
|
|
666
|
+
help="Skip the post-install setup script prompt",
|
|
667
|
+
is_flag=True,
|
|
668
|
+
),
|
|
607
669
|
):
|
|
608
670
|
"""Install a solution."""
|
|
609
671
|
solutions_dir = ctx.obj.get("solutions_dir") if ctx.obj else None
|
|
@@ -639,10 +701,24 @@ def install(
|
|
|
639
701
|
typer.echo(failed_m.group(0))
|
|
640
702
|
typer.echo(f"\n {total_imported} imported, {total_failed} failed")
|
|
641
703
|
else:
|
|
642
|
-
typer.echo(f" {total_imported}
|
|
704
|
+
typer.echo(f" {total_imported} entities imported")
|
|
643
705
|
else:
|
|
644
706
|
typer.echo(output)
|
|
645
707
|
|
|
708
|
+
# Post-install setup hook — runs before the informational menu
|
|
709
|
+
if not no_prompt and not skip_post_install_setup and _has_post_install(solution, solutions_dir):
|
|
710
|
+
state_file = Path.home() / ".cortex" / "solutions" / f"{solution}.json"
|
|
711
|
+
if state_file.exists():
|
|
712
|
+
typer.echo(f"\nRetrieving previous responses from: {state_file}")
|
|
713
|
+
desc = _get_setup_description(solution, solutions_dir)
|
|
714
|
+
typer.echo(f"\n{desc}")
|
|
715
|
+
if typer.confirm("Run setup now?", default=True):
|
|
716
|
+
_run_post_install_script(solution, solutions_dir=solutions_dir, ctx=ctx)
|
|
717
|
+
else:
|
|
718
|
+
typer.echo(f"\nRun setup later with: cortex solutions post-install -s {solution}")
|
|
719
|
+
elif skip_post_install_setup and _has_post_install(solution, solutions_dir):
|
|
720
|
+
typer.echo(f"\nRun setup later with: cortex solutions post-install -s {solution}")
|
|
721
|
+
|
|
646
722
|
if not no_prompt:
|
|
647
723
|
readme = _get_readme(solution, solutions_dir)
|
|
648
724
|
if readme:
|
|
@@ -660,6 +736,27 @@ def install(
|
|
|
660
736
|
_post_install_menu(readme, import_report=output, entity_tags=entity_tags, ui_url=ui_url)
|
|
661
737
|
|
|
662
738
|
|
|
739
|
+
@app.command(name="post-install")
|
|
740
|
+
def post_install(
|
|
741
|
+
ctx: typer.Context,
|
|
742
|
+
solution: str = typer.Option(..., "--solution", "-s", help="Solution tag"),
|
|
743
|
+
no_prompt: bool = typer.Option(
|
|
744
|
+
False,
|
|
745
|
+
"--no-prompt",
|
|
746
|
+
"-N",
|
|
747
|
+
help="Use saved answers from ~/.cortex/solutions/<tag>.json without prompting.",
|
|
748
|
+
),
|
|
749
|
+
):
|
|
750
|
+
"""Run post-install setup for a solution."""
|
|
751
|
+
solutions_dir = ctx.obj.get("solutions_dir") if ctx.obj else None
|
|
752
|
+
if solution not in _list_solution_tags(solutions_dir):
|
|
753
|
+
avail = ", ".join(_list_solution_tags(solutions_dir))
|
|
754
|
+
typer.echo(f"Error: Solution '{solution}' not found. Available: {avail}")
|
|
755
|
+
raise typer.Exit(1)
|
|
756
|
+
ctx.obj["client"] = _build_client(ctx)
|
|
757
|
+
_run_post_install_script(solution, solutions_dir=solutions_dir, ctx=ctx, no_prompt=no_prompt)
|
|
758
|
+
|
|
759
|
+
|
|
663
760
|
@app.command()
|
|
664
761
|
def uninstall(
|
|
665
762
|
ctx: typer.Context,
|
|
File without changes
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SolutionSetup(ABC):
|
|
11
|
+
"""
|
|
12
|
+
Base class for solution post-install setup scripts.
|
|
13
|
+
Subclasses define solution_tag, collect_prompts(), and steps().
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
solution_tag: str # must be set by subclass
|
|
17
|
+
|
|
18
|
+
def __init__(self, state_dir: Optional[Path] = None, no_prompt: bool = False):
|
|
19
|
+
self._no_prompt = no_prompt
|
|
20
|
+
self._save_answers = True
|
|
21
|
+
self._secret_keys: set = set()
|
|
22
|
+
self._answers: dict = {}
|
|
23
|
+
|
|
24
|
+
solutions_dir = state_dir or Path.home() / ".cortex" / "solutions"
|
|
25
|
+
solutions_dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
self._state_file = solutions_dir / f"{self.solution_tag}.json"
|
|
27
|
+
|
|
28
|
+
data = self._load_file()
|
|
29
|
+
self._answers = data.get("answers", {})
|
|
30
|
+
self._state: dict = data.get("state", {})
|
|
31
|
+
|
|
32
|
+
self._migrate_old_state()
|
|
33
|
+
|
|
34
|
+
def _load_file(self) -> dict:
|
|
35
|
+
if self._state_file.exists():
|
|
36
|
+
try:
|
|
37
|
+
return json.loads(self._state_file.read_text())
|
|
38
|
+
except (json.JSONDecodeError, OSError):
|
|
39
|
+
return {}
|
|
40
|
+
return {}
|
|
41
|
+
|
|
42
|
+
def _save_file(self) -> None:
|
|
43
|
+
saved_answers = (
|
|
44
|
+
{k: v for k, v in self._answers.items() if k not in self._secret_keys}
|
|
45
|
+
if self._save_answers
|
|
46
|
+
else {}
|
|
47
|
+
)
|
|
48
|
+
data = {"answers": saved_answers, "state": self._state}
|
|
49
|
+
self._state_file.write_text(json.dumps(data, indent=2))
|
|
50
|
+
|
|
51
|
+
def _save_state(self) -> None:
|
|
52
|
+
self._save_file()
|
|
53
|
+
|
|
54
|
+
def _migrate_old_state(self) -> None:
|
|
55
|
+
"""Move state from the old flat ~/.cortex/setup-<tag>.json into the new file."""
|
|
56
|
+
old_file = Path.home() / ".cortex" / f"setup-{self.solution_tag}.json"
|
|
57
|
+
if not old_file.exists():
|
|
58
|
+
return
|
|
59
|
+
try:
|
|
60
|
+
old_data = json.loads(old_file.read_text())
|
|
61
|
+
if old_data and not self._state:
|
|
62
|
+
self._state.update(old_data)
|
|
63
|
+
self._save_file()
|
|
64
|
+
old_file.unlink()
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def prompt(
|
|
69
|
+
self,
|
|
70
|
+
key: str,
|
|
71
|
+
message: str,
|
|
72
|
+
env_var: Optional[str] = None,
|
|
73
|
+
default: Optional[str] = None,
|
|
74
|
+
secret: bool = False,
|
|
75
|
+
hidden: bool = False,
|
|
76
|
+
) -> str:
|
|
77
|
+
"""Prompt for a value. Uses saved answer or env var when available.
|
|
78
|
+
|
|
79
|
+
secret=True — mask input AND exclude from saved JSON
|
|
80
|
+
hidden=True — mask input only; value is still saved to JSON
|
|
81
|
+
"""
|
|
82
|
+
use_getpass = secret or hidden
|
|
83
|
+
if secret:
|
|
84
|
+
self._secret_keys.add(key)
|
|
85
|
+
|
|
86
|
+
# Use saved answer when --no-prompt (works for both plain and hidden keys)
|
|
87
|
+
if self._no_prompt and not secret and key in self._answers:
|
|
88
|
+
return self._answers[key]
|
|
89
|
+
|
|
90
|
+
# Saved answer takes precedence over any derived default
|
|
91
|
+
if not secret and key in self._answers:
|
|
92
|
+
default = self._answers[key]
|
|
93
|
+
|
|
94
|
+
if env_var:
|
|
95
|
+
env_val = os.environ.get(env_var)
|
|
96
|
+
if env_val:
|
|
97
|
+
masked = "********" if use_getpass else env_val
|
|
98
|
+
if self._no_prompt or self.confirm(f"{message} [{masked} from {env_var}]", default=True):
|
|
99
|
+
self._answers[key] = env_val
|
|
100
|
+
return env_val
|
|
101
|
+
|
|
102
|
+
# Secrets in --no-prompt mode still need a prompt if no env var provided
|
|
103
|
+
if self._no_prompt and secret and key not in self._answers:
|
|
104
|
+
print(f" (secret required — no env var set for {key})", file=sys.stderr)
|
|
105
|
+
|
|
106
|
+
if use_getpass and default:
|
|
107
|
+
hint = f"********{default[-4:]}"
|
|
108
|
+
elif default:
|
|
109
|
+
hint = default
|
|
110
|
+
else:
|
|
111
|
+
hint = None
|
|
112
|
+
prompt_str = f"{message} [{hint}]: " if hint else f"{message}: "
|
|
113
|
+
|
|
114
|
+
if use_getpass:
|
|
115
|
+
value = getpass.getpass(prompt_str).strip()
|
|
116
|
+
else:
|
|
117
|
+
value = input(prompt_str).strip()
|
|
118
|
+
if not value:
|
|
119
|
+
value = default or ""
|
|
120
|
+
self._answers[key] = value
|
|
121
|
+
return value
|
|
122
|
+
|
|
123
|
+
def confirm(self, message: str, default: bool = True) -> bool:
|
|
124
|
+
"""Y|N confirmation prompt. Auto-accepts default when --no-prompt."""
|
|
125
|
+
if self._no_prompt:
|
|
126
|
+
return default
|
|
127
|
+
hint = "[Y/n]" if default else "[y/N]"
|
|
128
|
+
response = input(f"{message} {hint}: ").strip().lower()
|
|
129
|
+
if not response:
|
|
130
|
+
return default
|
|
131
|
+
return response in ("y", "yes")
|
|
132
|
+
|
|
133
|
+
def already_done(self, key: str) -> bool:
|
|
134
|
+
"""Return True if this step was previously completed."""
|
|
135
|
+
return self._state.get(key, False)
|
|
136
|
+
|
|
137
|
+
def mark_done(self, key: str) -> None:
|
|
138
|
+
"""Mark a step as completed in the persistent state file."""
|
|
139
|
+
self._state[key] = True
|
|
140
|
+
self._save_file()
|
|
141
|
+
|
|
142
|
+
@abstractmethod
|
|
143
|
+
def collect_prompts(self) -> None:
|
|
144
|
+
"""Collect all user inputs upfront before executing steps."""
|
|
145
|
+
|
|
146
|
+
@abstractmethod
|
|
147
|
+
def steps(self) -> list[tuple[str, callable]]:
|
|
148
|
+
"""Return ordered list of (label, callable) tuples."""
|
|
149
|
+
|
|
150
|
+
def post_steps(self) -> None:
|
|
151
|
+
"""Optional hook called after all steps complete. Override in subclass."""
|
|
152
|
+
|
|
153
|
+
def run(self) -> None:
|
|
154
|
+
"""Collect prompts then execute steps with progress display."""
|
|
155
|
+
if self._no_prompt and self._answers:
|
|
156
|
+
saved = {k: v for k, v in self._answers.items() if k not in self._secret_keys}
|
|
157
|
+
if saved:
|
|
158
|
+
print("Using saved configuration:")
|
|
159
|
+
for k, v in saved.items():
|
|
160
|
+
print(f" {k}: {v}")
|
|
161
|
+
print()
|
|
162
|
+
elif not self._no_prompt:
|
|
163
|
+
self._save_answers = self.confirm(f"Save answers for future runs to {self._state_file}", default=True)
|
|
164
|
+
|
|
165
|
+
self.collect_prompts()
|
|
166
|
+
self._save_file()
|
|
167
|
+
|
|
168
|
+
print()
|
|
169
|
+
step_list = self.steps()
|
|
170
|
+
total = len(step_list)
|
|
171
|
+
for i, (label, fn) in enumerate(step_list, 1):
|
|
172
|
+
try:
|
|
173
|
+
detail = fn()
|
|
174
|
+
print(f"[{i}/{total}] {label}... \u2713")
|
|
175
|
+
if detail:
|
|
176
|
+
lines = [detail] if isinstance(detail, str) else detail
|
|
177
|
+
for line in lines:
|
|
178
|
+
print(f" {line}")
|
|
179
|
+
print()
|
|
180
|
+
except Exception as e:
|
|
181
|
+
print(f"[{i}/{total}] {label}... \u2717 {e}", file=sys.stderr)
|
|
182
|
+
raise SystemExit(1)
|
|
183
|
+
self.post_steps()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: GitHub Actions Deploy Tracking
|
|
3
|
+
description: Track deployments from GitHub Actions in Cortex, with a deploy health scorecard measuring delivery cadence.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GitHub Actions Deploy Tracking
|
|
7
|
+
|
|
8
|
+
Trigger deploys from Cortex, track them as they run in GitHub Actions, and surface deploy health back in your service catalog.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
┌─────────────────────────────────┐
|
|
12
|
+
│ Cortex Catalog │
|
|
13
|
+
│ │
|
|
14
|
+
│ github-actions-demo (service) │
|
|
15
|
+
│ ├── x-cortex-git.github │
|
|
16
|
+
│ │ repository: owner/repo │
|
|
17
|
+
│ └── Scorecard: Deploy Health │
|
|
18
|
+
│ Bronze / Silver / Gold │
|
|
19
|
+
└──────────────┬──────────────────┘
|
|
20
|
+
│
|
|
21
|
+
│ Run workflow from entity page
|
|
22
|
+
│ (or: cortex workflows run -t
|
|
23
|
+
│ github-actions-deploy
|
|
24
|
+
│ --scope ENTITY --entity <tag>)
|
|
25
|
+
▼
|
|
26
|
+
┌─────────────────────────────────┐
|
|
27
|
+
│ Cortex Workflow │
|
|
28
|
+
│ Solution: Add Cortex Deploy │
|
|
29
|
+
│ from GitHub Actions │
|
|
30
|
+
│ │
|
|
31
|
+
│ 1. Read linked repo from │
|
|
32
|
+
│ entity catalog config │
|
|
33
|
+
│ 2. POST workflow_dispatch │
|
|
34
|
+
│ to GitHub Actions │
|
|
35
|
+
│ 3. Wait for callback │
|
|
36
|
+
└──────────────┬──────────────────┘
|
|
37
|
+
│ POST /dispatches (GitHub integration)
|
|
38
|
+
▼
|
|
39
|
+
┌─────────────────────────────────┐
|
|
40
|
+
│ GitHub Actions │
|
|
41
|
+
│ cortex-deploy.yml │
|
|
42
|
+
│ │
|
|
43
|
+
│ job: build │
|
|
44
|
+
│ └── run your deploy steps │
|
|
45
|
+
│ │
|
|
46
|
+
│ job: cortex-callback │
|
|
47
|
+
│ ├── POST /deploys │◄── registers deploy event
|
|
48
|
+
│ │ (entity: github-actions- │ on the Cortex entity
|
|
49
|
+
│ │ demo) │
|
|
50
|
+
│ └── POST callbackUrl ───────┼──► Cortex marks workflow
|
|
51
|
+
│ status: SUCCESS/FAILURE │ run complete
|
|
52
|
+
└─────────────────────────────────┘
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## What's Included
|
|
56
|
+
|
|
57
|
+
- **Entity:** `github-actions-demo` service — a sample entity to receive deploy events
|
|
58
|
+
- **Scorecard:** Deploy Health — Bronze/Silver/Gold based on deploy frequency
|
|
59
|
+
- **GitHub Actions workflow:** `cortex-deploy.yml` — a two-job workflow (build → notify) seeded into your GitHub repo
|
|
60
|
+
- **Cortex workflow:** `github-actions-deploy` — reads the linked repo from the entity, triggers the GitHub Actions deploy, and waits for the result
|
|
61
|
+
- **Setup script:** Interactive wizard that creates and seeds a GitHub repo end-to-end
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
1. Install the solution:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
cortex solutions install -s github-actions-deploy
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
2. Follow the post-install setup prompts, or run later:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
cortex solutions post-install -s github-actions-deploy
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## How It Works
|
|
78
|
+
|
|
79
|
+
The Cortex workflow reads `x-cortex-git.github.repository` from the entity's catalog config to determine which GitHub repo to deploy. It triggers `cortex-deploy.yml` via `workflow_dispatch` and waits asynchronously for a callback.
|
|
80
|
+
|
|
81
|
+
GitHub Actions runs the build, then notifies Cortex twice on completion:
|
|
82
|
+
- **Deploy registration** (`POST /api/v1/catalog/{tag}/deploys`) — records the deploy event on the entity, feeding the Deploy Health scorecard
|
|
83
|
+
- **Workflow callback** — signals the Cortex workflow run as SUCCESS or FAILURE
|
|
84
|
+
|
|
85
|
+
## After Installing
|
|
86
|
+
|
|
87
|
+
If you ran the post-install setup, you're already done — it created the GitHub repo, seeded the workflow, set secrets, linked the entity, and triggered a test deploy.
|
|
88
|
+
|
|
89
|
+
To roll the pattern out to your own services, add `cortex-deploy.yml` to any GitHub repo and link that repo to its Cortex entity. The same **Solution: Add Cortex Deploy from GitHub Actions** workflow will work across all of them — it reads the linked repo from the entity automatically.
|
|
90
|
+
|
|
91
|
+
## Customizing for Production
|
|
92
|
+
|
|
93
|
+
- Point the workflow at your real entity by replacing `github-actions-demo` with your service tag
|
|
94
|
+
- Add `CORTEX_API_KEY` and `CORTEX_BASE_URL` secrets to your real repos
|
|
95
|
+
- The Deploy Health scorecard is scoped to `demo-github-actions-deploys` to avoid affecting your
|
|
96
|
+
existing services. To roll it out broadly, remove the group filter from the scorecard.
|
|
97
|
+
To opt in individual services, add the `demo-github-actions-deploys` group to them.
|