cortexapps-cli 1.30.0__tar.gz → 1.31.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.31.0}/PKG-INFO +2 -1
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/solutions.py +111 -14
- cortexapps_cli-1.31.0/cortexapps_cli/solutions/_lib/__init__.py +0 -0
- cortexapps_cli-1.31.0/cortexapps_cli/solutions/_lib/setup_base.py +169 -0
- cortexapps_cli-1.31.0/cortexapps_cli/solutions/github-actions-deploy/README.md +97 -0
- cortexapps_cli-1.31.0/cortexapps_cli/solutions/github-actions-deploy/_templates/cortex-async-callback.yml +39 -0
- cortexapps_cli-1.31.0/cortexapps_cli/solutions/github-actions-deploy/_templates/cortex-deploy.yml +51 -0
- cortexapps_cli-1.31.0/cortexapps_cli/solutions/github-actions-deploy/_templates/cortex-register-deploy.yml +51 -0
- cortexapps_cli-1.31.0/cortexapps_cli/solutions/github-actions-deploy/_templates/github-actions-deploy.yaml +132 -0
- cortexapps_cli-1.31.0/cortexapps_cli/solutions/github-actions-deploy/catalog/github-actions-demo.yaml +9 -0
- cortexapps_cli-1.31.0/cortexapps_cli/solutions/github-actions-deploy/scorecards/deploy-health.yaml +51 -0
- cortexapps_cli-1.31.0/cortexapps_cli/solutions/github-actions-deploy/setup.py +511 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/pyproject.toml +2 -1
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/LICENSE +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/README.rst +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/__init__.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/cli.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/command_options.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/ai_skills.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/api_keys.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/audit_logs.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/backup.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/backup_commands/cortex_export.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/catalog.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/custom_data.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/custom_events.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/custom_metrics.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/dependencies.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/deploys.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/discovery_audit.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/docs.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/entity_relationship_types.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/entity_relationships.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/entity_types.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/gitops_logs.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/groups.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/initiatives.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/apiiro.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/argocd.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/aws.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/azure_active_directory.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/azure_devops.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/azure_resources.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/bamboohr.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/bitbucket.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/bugsnag.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/buildkite.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/checkmarx_sast.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/circleci.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/clickup.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/codecov.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/coralogix.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/datadog.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/dynatrace.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/firehydrant.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/github.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/gitlab.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/incidentio.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/instana.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/jenkins.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/jira.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/launchdarkly.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/mend_sast.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/mend_sca.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/newrelic.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/okta.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/opsgenie.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/pagerduty.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/prometheus.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/rollbar.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/rootly.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/semgrep.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/sentry.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/servicenow.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/servicenow_cloud_observability.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/snyk.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/sonarqube.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/splunk_observability_cloud.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/splunk_on_call.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/sumologic.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/veracode.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/wiz.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/workday.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/integrations_commands/xmatters.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/ip_allowlist.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/on_call.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/packages.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/packages_commands/go.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/packages_commands/java.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/packages_commands/node.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/packages_commands/nuget.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/packages_commands/python.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/plugins.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/queries.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/rest.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/scim.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/scorecards.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/scorecards_commands/exemptions.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/secrets.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/teams.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/users.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/users_commands/roles.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/commands/workflows.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/cortex_client.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/models/team.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/__init__.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/README.md +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/catalog/address-reviews.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/catalog/audit-flags.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/catalog/code-review.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/catalog/fix-issue.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/catalog/incident-trends.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/catalog/jira-to-kb.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/catalog/release-to-sha.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/catalog/search-troubleshooting-kb.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/catalog/simplify-test.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/entity-types/ai-agent.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-agents/scorecards/ai-agent-governance.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-skills/README.md +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-skills/catalog/api-doc-sync.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-skills/catalog/changelog-writer.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-skills/catalog/docs-generator.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-skills/catalog/docs-portal.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-skills/entity-relationship-types/ai-plugin-service.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-skills/entity-relationship-types/ai-plugin-skills.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-skills/entity-relationship-types/ai-skill-service.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-skills/entity-types/ai-plugin.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-skills/entity-types/ai-skill.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-skills/scorecards/ai-skills-quality.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/.github/workflows/sync-claude-spend.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/README.md +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-alice-chen.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-bob-martinez.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-carol-kim.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-david-osei.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-emma-johnson.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/catalog/team-data.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/catalog/team-engineering.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/catalog/team-frontend.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/catalog/team-platform.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/custom-metrics/ai-spend.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/entity-relationship-types/team-member.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/entity-types/employee.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/plugins/team-ai-spend.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/scorecards/ai-spend-scorecard.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/ai-spend/scripts/sync-claude-spend.py +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/.github/workflows/cortex-service-version.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/README.md +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/api-gateway-3.0.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/api-gateway-3.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/api-gateway-3.1.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/api-gateway.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/auth-2.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/auth-2.1.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/auth-2.2.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/auth.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/aws-dev-us-west-2.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/billing-1.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/billing-1.2.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/billing.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/cart-1.0.2.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/cart.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/checkout-2.2.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/checkout-2.3.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/checkout-2.4.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/checkout.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/gcp-prod-us-east-1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/gcp-prod-us-west-1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/gcp-staging-us-east-1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/notifications-1.0.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/notifications-1.0.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/notifications-1.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/notifications.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/order-service-2.0.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/order-service-2.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/order-service.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/payments-1.6.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/payments-1.7.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/payments-1.7.1.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/payments.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/release-2026-06-20.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/release-2026-07-01.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/release-2026-07-09.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/release-2026-07-10.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/search-4.0.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/search-4.1.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/search.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/user-service-1.2.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/user-service-1.3.0.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/catalog/user-service.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/entity-relationship-types/environments.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/entity-relationship-types/versions.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/entity-types/environment.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/entity-types/release.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/entity-types/service-version.json +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/workflows/create-environment.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/workflows/create-release.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.0}/cortexapps_cli/solutions/environments/workflows/trigger-github-release.yaml +0 -0
- {cortexapps_cli-1.30.0 → cortexapps_cli-1.31.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.31.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)
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -643,6 +705,20 @@ def install(
|
|
|
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,169 @@
|
|
|
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._secret_keys: set = set()
|
|
21
|
+
self._answers: dict = {}
|
|
22
|
+
|
|
23
|
+
solutions_dir = state_dir or Path.home() / ".cortex" / "solutions"
|
|
24
|
+
solutions_dir.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
self._state_file = solutions_dir / f"{self.solution_tag}.json"
|
|
26
|
+
|
|
27
|
+
data = self._load_file()
|
|
28
|
+
self._answers = data.get("answers", {})
|
|
29
|
+
self._state: dict = data.get("state", {})
|
|
30
|
+
|
|
31
|
+
self._migrate_old_state()
|
|
32
|
+
|
|
33
|
+
def _load_file(self) -> dict:
|
|
34
|
+
if self._state_file.exists():
|
|
35
|
+
try:
|
|
36
|
+
return json.loads(self._state_file.read_text())
|
|
37
|
+
except (json.JSONDecodeError, OSError):
|
|
38
|
+
return {}
|
|
39
|
+
return {}
|
|
40
|
+
|
|
41
|
+
def _save_file(self) -> None:
|
|
42
|
+
data = {
|
|
43
|
+
"answers": {k: v for k, v in self._answers.items() if k not in self._secret_keys},
|
|
44
|
+
"state": self._state,
|
|
45
|
+
}
|
|
46
|
+
self._state_file.write_text(json.dumps(data, indent=2))
|
|
47
|
+
|
|
48
|
+
def _save_state(self) -> None:
|
|
49
|
+
self._save_file()
|
|
50
|
+
|
|
51
|
+
def _migrate_old_state(self) -> None:
|
|
52
|
+
"""Move state from the old flat ~/.cortex/setup-<tag>.json into the new file."""
|
|
53
|
+
old_file = Path.home() / ".cortex" / f"setup-{self.solution_tag}.json"
|
|
54
|
+
if not old_file.exists():
|
|
55
|
+
return
|
|
56
|
+
try:
|
|
57
|
+
old_data = json.loads(old_file.read_text())
|
|
58
|
+
if old_data and not self._state:
|
|
59
|
+
self._state.update(old_data)
|
|
60
|
+
self._save_file()
|
|
61
|
+
old_file.unlink()
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def prompt(
|
|
66
|
+
self,
|
|
67
|
+
key: str,
|
|
68
|
+
message: str,
|
|
69
|
+
env_var: Optional[str] = None,
|
|
70
|
+
default: Optional[str] = None,
|
|
71
|
+
secret: bool = False,
|
|
72
|
+
) -> str:
|
|
73
|
+
"""Prompt for a value. Uses saved answer or env var when available."""
|
|
74
|
+
if secret:
|
|
75
|
+
self._secret_keys.add(key)
|
|
76
|
+
|
|
77
|
+
# Non-secret: use saved answer when --no-prompt
|
|
78
|
+
if self._no_prompt and not secret and key in self._answers:
|
|
79
|
+
return self._answers[key]
|
|
80
|
+
|
|
81
|
+
# Non-secret: saved answer takes precedence over any derived default
|
|
82
|
+
if not secret and key in self._answers:
|
|
83
|
+
default = self._answers[key]
|
|
84
|
+
|
|
85
|
+
if env_var:
|
|
86
|
+
env_val = os.environ.get(env_var)
|
|
87
|
+
if env_val:
|
|
88
|
+
masked = "********" if secret else env_val
|
|
89
|
+
if self._no_prompt or self.confirm(f"{message} [{masked} from {env_var}]", default=True):
|
|
90
|
+
self._answers[key] = env_val
|
|
91
|
+
return env_val
|
|
92
|
+
|
|
93
|
+
# Secrets in --no-prompt mode still need a prompt if no env var provided
|
|
94
|
+
if self._no_prompt and secret and key not in self._answers:
|
|
95
|
+
print(f" (secret required — no env var set for {key})", file=sys.stderr)
|
|
96
|
+
|
|
97
|
+
prompt_str = message
|
|
98
|
+
if default:
|
|
99
|
+
prompt_str += f" [{default}]"
|
|
100
|
+
prompt_str += ": "
|
|
101
|
+
|
|
102
|
+
if secret:
|
|
103
|
+
value = getpass.getpass(prompt_str).strip()
|
|
104
|
+
else:
|
|
105
|
+
value = input(prompt_str).strip()
|
|
106
|
+
if not value:
|
|
107
|
+
value = default or ""
|
|
108
|
+
self._answers[key] = value
|
|
109
|
+
return value
|
|
110
|
+
|
|
111
|
+
def confirm(self, message: str, default: bool = True) -> bool:
|
|
112
|
+
"""Y|N confirmation prompt. Auto-accepts default when --no-prompt."""
|
|
113
|
+
if self._no_prompt:
|
|
114
|
+
return default
|
|
115
|
+
hint = "[Y/n]" if default else "[y/N]"
|
|
116
|
+
response = input(f"{message} {hint}: ").strip().lower()
|
|
117
|
+
if not response:
|
|
118
|
+
return default
|
|
119
|
+
return response in ("y", "yes")
|
|
120
|
+
|
|
121
|
+
def already_done(self, key: str) -> bool:
|
|
122
|
+
"""Return True if this step was previously completed."""
|
|
123
|
+
return self._state.get(key, False)
|
|
124
|
+
|
|
125
|
+
def mark_done(self, key: str) -> None:
|
|
126
|
+
"""Mark a step as completed in the persistent state file."""
|
|
127
|
+
self._state[key] = True
|
|
128
|
+
self._save_file()
|
|
129
|
+
|
|
130
|
+
@abstractmethod
|
|
131
|
+
def collect_prompts(self) -> None:
|
|
132
|
+
"""Collect all user inputs upfront before executing steps."""
|
|
133
|
+
|
|
134
|
+
@abstractmethod
|
|
135
|
+
def steps(self) -> list[tuple[str, callable]]:
|
|
136
|
+
"""Return ordered list of (label, callable) tuples."""
|
|
137
|
+
|
|
138
|
+
def post_steps(self) -> None:
|
|
139
|
+
"""Optional hook called after all steps complete. Override in subclass."""
|
|
140
|
+
|
|
141
|
+
def run(self) -> None:
|
|
142
|
+
"""Collect prompts then execute steps with progress display."""
|
|
143
|
+
if self._no_prompt and self._answers:
|
|
144
|
+
saved = {k: v for k, v in self._answers.items() if k not in self._secret_keys}
|
|
145
|
+
if saved:
|
|
146
|
+
print("Using saved configuration:")
|
|
147
|
+
for k, v in saved.items():
|
|
148
|
+
print(f" {k}: {v}")
|
|
149
|
+
print()
|
|
150
|
+
|
|
151
|
+
self.collect_prompts()
|
|
152
|
+
self._save_file()
|
|
153
|
+
|
|
154
|
+
print()
|
|
155
|
+
step_list = self.steps()
|
|
156
|
+
total = len(step_list)
|
|
157
|
+
for i, (label, fn) in enumerate(step_list, 1):
|
|
158
|
+
try:
|
|
159
|
+
detail = fn()
|
|
160
|
+
print(f"[{i}/{total}] {label}... \u2713")
|
|
161
|
+
if detail:
|
|
162
|
+
lines = [detail] if isinstance(detail, str) else detail
|
|
163
|
+
for line in lines:
|
|
164
|
+
print(f" {line}")
|
|
165
|
+
print()
|
|
166
|
+
except Exception as e:
|
|
167
|
+
print(f"[{i}/{total}] {label}... \u2717 {e}", file=sys.stderr)
|
|
168
|
+
raise SystemExit(1)
|
|
169
|
+
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.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Cortex Async Callback
|
|
2
|
+
description: Post a completion status back to a Cortex async workflow callback URL.
|
|
3
|
+
inputs:
|
|
4
|
+
cortex_api_key:
|
|
5
|
+
description: Cortex API key
|
|
6
|
+
required: true
|
|
7
|
+
cortex_callback_url:
|
|
8
|
+
description: Cortex async workflow callback URL
|
|
9
|
+
required: true
|
|
10
|
+
status:
|
|
11
|
+
description: Completion status — SUCCESS, FAILURE, or CANCELLED
|
|
12
|
+
required: true
|
|
13
|
+
message:
|
|
14
|
+
description: Optional status message shown in the Cortex workflow run
|
|
15
|
+
required: false
|
|
16
|
+
default: ''
|
|
17
|
+
sha:
|
|
18
|
+
description: Git commit SHA to include in callback output
|
|
19
|
+
required: false
|
|
20
|
+
default: ''
|
|
21
|
+
run_url:
|
|
22
|
+
description: GitHub Actions run URL to include in callback output
|
|
23
|
+
required: false
|
|
24
|
+
default: ''
|
|
25
|
+
runs:
|
|
26
|
+
using: composite
|
|
27
|
+
steps:
|
|
28
|
+
- name: Post callback
|
|
29
|
+
shell: bash
|
|
30
|
+
run: |
|
|
31
|
+
curl -s -f -X POST "${{ inputs.cortex_callback_url }}" \
|
|
32
|
+
-H "Authorization: Bearer ${{ inputs.cortex_api_key }}" \
|
|
33
|
+
-H "Content-Type: application/json" \
|
|
34
|
+
-d "$(jq -n \
|
|
35
|
+
--arg status "${{ inputs.status }}" \
|
|
36
|
+
--arg message "${{ inputs.message }}" \
|
|
37
|
+
--arg sha "${{ inputs.sha }}" \
|
|
38
|
+
--arg run_url "${{ inputs.run_url }}" \
|
|
39
|
+
'{status: $status, message: $message, output: {sha: $sha, run_url: $run_url}}')"
|
cortexapps_cli-1.31.0/cortexapps_cli/solutions/github-actions-deploy/_templates/cortex-deploy.yml
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Cortex Deploy
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
cortex_callback_url:
|
|
9
|
+
description: 'Cortex async workflow callback URL (set automatically when triggered via Cortex workflow)'
|
|
10
|
+
required: false
|
|
11
|
+
default: ''
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- name: Build
|
|
18
|
+
run: |
|
|
19
|
+
echo "Starting deployment..."
|
|
20
|
+
sleep 10
|
|
21
|
+
for i in 1 2 3; do
|
|
22
|
+
sleep 5
|
|
23
|
+
echo "Deployment progress: step $i/3"
|
|
24
|
+
done
|
|
25
|
+
echo "Build complete!"
|
|
26
|
+
|
|
27
|
+
cortex-callback:
|
|
28
|
+
needs: build
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
if: always()
|
|
31
|
+
steps:
|
|
32
|
+
- name: Checkout (for local actions)
|
|
33
|
+
uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- name: Register deploy in Cortex
|
|
36
|
+
if: needs.build.result == 'success'
|
|
37
|
+
uses: ./.github/actions/cortex-register-deploy
|
|
38
|
+
with:
|
|
39
|
+
cortex_api_key: ${{ secrets.CORTEX_API_KEY }}
|
|
40
|
+
cortex_base_url: ${{ secrets.CORTEX_BASE_URL }}
|
|
41
|
+
entity_tag: github-actions-demo
|
|
42
|
+
|
|
43
|
+
- name: Cortex async callback
|
|
44
|
+
if: inputs.cortex_callback_url != ''
|
|
45
|
+
uses: ./.github/actions/cortex-async-callback
|
|
46
|
+
with:
|
|
47
|
+
cortex_api_key: ${{ secrets.CORTEX_API_KEY }}
|
|
48
|
+
cortex_callback_url: ${{ inputs.cortex_callback_url }}
|
|
49
|
+
status: ${{ needs.build.result == 'success' && 'SUCCESS' || needs.build.result == 'cancelled' && 'CANCELLED' || 'FAILURE' }}
|
|
50
|
+
sha: ${{ github.sha }}
|
|
51
|
+
run_url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Cortex Register Deploy
|
|
2
|
+
description: Register a deploy event on a Cortex entity.
|
|
3
|
+
inputs:
|
|
4
|
+
cortex_api_key:
|
|
5
|
+
description: Cortex API key
|
|
6
|
+
required: true
|
|
7
|
+
cortex_base_url:
|
|
8
|
+
description: Cortex base URL
|
|
9
|
+
required: false
|
|
10
|
+
default: https://api.getcortexapp.com
|
|
11
|
+
entity_tag:
|
|
12
|
+
description: Cortex entity tag to register the deploy against
|
|
13
|
+
required: true
|
|
14
|
+
environment:
|
|
15
|
+
description: Deployment environment
|
|
16
|
+
required: false
|
|
17
|
+
default: production
|
|
18
|
+
sha:
|
|
19
|
+
description: Git commit SHA (defaults to github.sha)
|
|
20
|
+
required: false
|
|
21
|
+
default: ''
|
|
22
|
+
runs:
|
|
23
|
+
using: composite
|
|
24
|
+
steps:
|
|
25
|
+
- name: Register deploy
|
|
26
|
+
shell: bash
|
|
27
|
+
run: |
|
|
28
|
+
SHA="${{ inputs.sha }}"
|
|
29
|
+
SHA="${SHA:-${{ github.sha }}}"
|
|
30
|
+
curl -s -f -X POST \
|
|
31
|
+
"${{ inputs.cortex_base_url }}/api/v1/catalog/${{ inputs.entity_tag }}/deploys" \
|
|
32
|
+
-H "Authorization: Bearer ${{ inputs.cortex_api_key }}" \
|
|
33
|
+
-H "Content-Type: application/json" \
|
|
34
|
+
-d "$(jq -n \
|
|
35
|
+
--arg sha "$SHA" \
|
|
36
|
+
--arg actor "${{ github.actor }}" \
|
|
37
|
+
--arg branch "${{ github.ref_name }}" \
|
|
38
|
+
--arg run_id "${{ github.run_id }}" \
|
|
39
|
+
--arg run_url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
|
40
|
+
--arg trigger "${{ github.event_name }}" \
|
|
41
|
+
--arg env "${{ inputs.environment }}" \
|
|
42
|
+
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
43
|
+
'{
|
|
44
|
+
sha: $sha,
|
|
45
|
+
timestamp: $timestamp,
|
|
46
|
+
environment: $env,
|
|
47
|
+
type: "DEPLOY",
|
|
48
|
+
title: ("Triggered by " + $actor),
|
|
49
|
+
deployer: {name: $actor},
|
|
50
|
+
customData: {branch: $branch, runId: $run_id, runUrl: $run_url, trigger: $trigger}
|
|
51
|
+
}')"
|