nthlayer-generate 2.0.2__tar.gz → 2.0.4__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.
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/PKG-INFO +1 -1
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/pyproject.toml +1 -1
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/__init__.py +1 -1
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/init.py +202 -30
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/setup.py +165 -39
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/manifest.py +57 -2
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/validator.py +6 -5
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate.egg-info/PKG-INFO +1 -1
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate.egg-info/SOURCES.txt +1 -0
- nthlayer_generate-2.0.4/tests/test_cli_setup.py +1788 -0
- nthlayer_generate-2.0.4/tests/test_init.py +1729 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_init_documented_flags.py +6 -0
- nthlayer_generate-2.0.4/tests/test_source_citations.py +117 -0
- nthlayer_generate-2.0.2/tests/test_cli_setup.py +0 -944
- nthlayer_generate-2.0.2/tests/test_init.py +0 -621
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/README.md +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/setup.cfg +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/alertmanager/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/alertmanager/config.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/alerts/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/alerts/loader.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/alerts/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/alerts/validator.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/alerts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/apply.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/backstage.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/dashboard.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/dashboard_validate.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/dependencies.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/docs.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/environments.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/formatters/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/formatters/json_fmt.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/formatters/junit.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/formatters/markdown.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/formatters/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/formatters/sarif.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/generate.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/generate_alerts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/generate_loki.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/identity.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/lint.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/migrate.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/migrate_manifest.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/ownership.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/pagerduty.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/plan.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/recommend_metrics.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/recording_rules.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/simulate.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/slo.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/templates.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/topology.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/ux.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/validate.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/validate_metadata.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/validate_slo.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/cli/validate_spec.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/clients/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/clients/base.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/clients/cortex.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/clients/pagerduty.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/clients/slack.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/config/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/config/cli.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/config/integrations.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/config/loader.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/config/secrets/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/config/secrets/backends.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/config/settings.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/core/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/core/errors.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/core/tiers.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/builder_sdk.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/intents.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/manifest_builder.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/panel_spec.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/resolver.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/sdk_adapter.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/base.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/base_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/consul_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/elasticsearch_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/etcd_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/haproxy_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/http_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/kafka_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/kubernetes.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/mongodb_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/mysql_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/nats_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/nginx_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/postgresql_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/pulsar_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/rabbitmq_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/redis_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/stream_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/traefik_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/templates/worker_intent.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dashboards/validator.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/demo.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dependencies/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dependencies/discovery.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dependencies/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dependencies/providers/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dependencies/providers/backstage.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dependencies/providers/base.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dependencies/providers/consul.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dependencies/providers/etcd.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dependencies/providers/kubernetes.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dependencies/providers/prometheus.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/dependencies/providers/zookeeper.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/discovery/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/discovery/classifier.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/discovery/client.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/discovery/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/domain/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/domain/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/generators/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/generators/alerts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/generators/backstage.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/generators/docs.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/generators/sloth.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/normalizer.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/ownership.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/ownership_providers/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/ownership_providers/backstage.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/ownership_providers/base.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/ownership_providers/codeowners.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/ownership_providers/declared.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/ownership_providers/kubernetes.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/ownership_providers/pagerduty.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/identity/resolver.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/integrations/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/integrations/pagerduty.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/logging.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/loki/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/loki/generator.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/loki/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/loki/templates.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/discovery.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/recommender.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/runtime/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/runtime/go.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/runtime/jvm.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/runtime/nodejs.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/runtime/python.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/standards/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/standards/aliases.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/standards/otel_semconv.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/templates/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/templates/api.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/templates/cache.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/templates/database_client.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/templates/gateway.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/templates/grpc.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/templates/queue_consumer.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/templates/registry.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/metrics/templates/worker.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/orchestration/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/orchestration/engine.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/orchestration/handlers.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/orchestration/plan_builder.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/orchestration/registry.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/orchestration/results.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/orchestrator.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/pagerduty/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/pagerduty/defaults.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/pagerduty/naming.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/pagerduty/orchestration.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/pagerduty/resources.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/policies/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/policies/engine.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/policies/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/policies/rules.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/providers/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/providers/__main__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/providers/base.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/providers/cli.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/providers/grafana.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/providers/lock.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/providers/mimir.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/providers/pagerduty.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/providers/prometheus.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/providers/registry.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/recording_rules/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/recording_rules/builder.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/recording_rules/manifest_builder.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/recording_rules/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/simulate/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/simulate/engine.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/simulate/graph.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/simulate/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/simulate/output.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/simulate/what_if.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/slos/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/slos/alerts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/slos/calculator.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/slos/ceiling.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/slos/dependencies.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/slos/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/slos/parser.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/slos/pipeline.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/alerting.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/builtin_templates/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/contracts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/custom_templates.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/environment_alerts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/environment_detection.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/environment_gates.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/environment_merger.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/environments.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/helpers.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/loader.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/opensrm_parser.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/parser.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/template.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/template_loader.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/templates.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/specs/variable_substitution.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/topology/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/topology/enrichment.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/topology/models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/topology/serializers.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/user_config.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/validation/__init__.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/validation/conftest.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/validation/metadata.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/validation/promql.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate/validation/promruval.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate.egg-info/dependency_links.txt +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate.egg-info/entry_points.txt +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate.egg-info/requires.txt +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/src/nthlayer_generate.egg-info/top_level.txt +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_alert_pipeline.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_alerting_config.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_alertmanager.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_alerts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_backstage_generator.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_budget_policy.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_alerts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_apply.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_dashboard.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_dashboard_validate.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_environments.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_formatters.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_generate.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_generate_alerts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_generate_loki.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_identity.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_lint.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_migrate_manifest.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_pagerduty.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_plan.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_slo.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_templates.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_ux.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_validate.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_validate_metadata.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_validate_slo.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_cli_validate_spec.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_clients.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_config_cli.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_config_loader.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_config_secrets_backends.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_config_secrets_init.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_conftest_validation.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_contract_validation.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_core_errors.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_core_tiers.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_critical_models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_custom_templates.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_dashboard_generation.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_dashboard_templates.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_dashboards_builder_sdk.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_dashboards_models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_dashboards_resolver.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_dashboards_validator.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_demo.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_demo_app_auth.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_dependencies.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_dependency_declarations.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_deps_discovery.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_deps_provider_backstage.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_deps_provider_consul.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_deps_provider_etcd.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_deps_provider_kubernetes.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_deps_provider_zookeeper.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_discovery_client.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_docs_generator.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_domain_models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_env_flag.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_environment_commands.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_generators_alerts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_generators_backstage.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_generators_docs.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_generators_sloth.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_hybrid_dashboard_builder.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_identity.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_integrations_pagerduty.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_intent_templates.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_loki.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_metadata_validation.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_metrics_discovery.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_metrics_recommender.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_mimir_provider.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_multi_environment_integration.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_opensrm.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_orchestrator.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_ownership.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_pagerduty.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_pagerduty_module.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_pagerduty_orchestration.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_pagerduty_resources.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_policy_engine.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_policy_handler.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_policy_models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_policy_rules.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_prometheus_provider.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_provider_grafana.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_provider_pagerduty.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_providers_lock.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_recording_rules.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_sdk_adapter.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_service_type_agreement.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_simulate_cli.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_simulate_engine.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_simulate_graph.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_simulate_models.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_simulate_what_if.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_slo_alerts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_slo_ceiling.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_slo_cli.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_slos.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_specs.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_sync_awesome_alerts.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_templates.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_topology.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_user_config.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_validation.py +0 -0
- {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.4}/tests/test_validation_promruval.py +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""CLI command for initializing new NthLayer services."""
|
|
2
2
|
|
|
3
|
+
import re
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
6
|
+
import yaml
|
|
5
7
|
from nthlayer_common.manifest.models import resolve_service_type
|
|
6
8
|
|
|
7
9
|
from nthlayer_generate.cli.ux import (
|
|
@@ -17,6 +19,7 @@ from nthlayer_generate.cli.ux import (
|
|
|
17
19
|
)
|
|
18
20
|
from nthlayer_generate.core.tiers import TIER_CONFIGS
|
|
19
21
|
from nthlayer_generate.specs.custom_templates import CustomTemplateLoader
|
|
22
|
+
from nthlayer_generate.specs.manifest import is_valid_service_name
|
|
20
23
|
|
|
21
24
|
# The service-type menu. INVARIANT: every key is a value a manifest can
|
|
22
25
|
# store verbatim — nothing here is translated on the way out (opensrm-8qpd).
|
|
@@ -115,12 +118,22 @@ def init_command(
|
|
|
115
118
|
if not team and interactive:
|
|
116
119
|
team = text_input("Team name", placeholder="e.g., platform, payments")
|
|
117
120
|
|
|
118
|
-
|
|
121
|
+
# `not team` first: team is `str | None` here, so `.strip()` alone would
|
|
122
|
+
# raise on None and also lose the narrowing the calls below rely on. The
|
|
123
|
+
# `.strip()` half is what catches a whitespace-only --team here, where the
|
|
124
|
+
# message fits it, rather than at the _is_valid_team branch below, whose
|
|
125
|
+
# message would be false for it.
|
|
126
|
+
if not team or not team.strip():
|
|
119
127
|
error("Team name is required")
|
|
120
128
|
if not interactive:
|
|
121
129
|
console.print(" [muted]Pass it with --team <team>[/muted]")
|
|
122
130
|
return 1
|
|
123
131
|
|
|
132
|
+
if not _is_valid_team(team):
|
|
133
|
+
error("Invalid team name")
|
|
134
|
+
console.print(" [muted]Team name must not contain line breaks, tabs or NUL[/muted]")
|
|
135
|
+
return 1
|
|
136
|
+
|
|
124
137
|
# Select service tier using interactive menu
|
|
125
138
|
tier = None
|
|
126
139
|
if interactive:
|
|
@@ -196,10 +209,25 @@ def init_command(
|
|
|
196
209
|
service_name, team, tier, service_type, dependencies, template_obj
|
|
197
210
|
)
|
|
198
211
|
|
|
212
|
+
# encoding="utf-8" explicitly, and UnicodeError caught alongside OSError
|
|
213
|
+
# (opensrm-t4rd edge-case pass). Without the encoding, `write_text` uses the
|
|
214
|
+
# locale's, so on a non-UTF-8 system an ordinary accented team name raised
|
|
215
|
+
# UnicodeEncodeError -- which is a ValueError, NOT an OSError, so the handler
|
|
216
|
+
# below missed it and init died by traceback. Worse, `write_text` had already
|
|
217
|
+
# created the file, so a ZERO-BYTE manifest was left behind and every later
|
|
218
|
+
# run then hit the `exists()` guard above and refused, permanently, until
|
|
219
|
+
# someone deleted it by hand.
|
|
220
|
+
#
|
|
221
|
+
# The unlink is the rollback half of this bead's defect 3: a run that fails
|
|
222
|
+
# must not leave a partial artifact that blocks the next one.
|
|
199
223
|
try:
|
|
200
|
-
service_file.write_text(service_content)
|
|
201
|
-
except OSError as e:
|
|
224
|
+
service_file.write_text(service_content, encoding="utf-8")
|
|
225
|
+
except (OSError, UnicodeError) as e:
|
|
202
226
|
error(f"Error creating service file: {e}")
|
|
227
|
+
try:
|
|
228
|
+
service_file.unlink(missing_ok=True)
|
|
229
|
+
except OSError:
|
|
230
|
+
pass
|
|
203
231
|
return 1
|
|
204
232
|
|
|
205
233
|
# Create .nthlayer directory
|
|
@@ -209,20 +237,40 @@ def init_command(
|
|
|
209
237
|
except OSError as e:
|
|
210
238
|
warning(f"Could not create .nthlayer directory: {e}")
|
|
211
239
|
|
|
212
|
-
# Create config file if it doesn't exist
|
|
240
|
+
# Create config file if it doesn't exist.
|
|
241
|
+
#
|
|
242
|
+
# `is_file()`, NOT `exists()` -- the same confusion this bead fixed for
|
|
243
|
+
# `nthlayer_dir` below. With `.nthlayer/config.yaml` present as a DIRECTORY,
|
|
244
|
+
# `exists()` is True because it is a directory, so the write was skipped and
|
|
245
|
+
# init reported success for a config it had never written, with no warning.
|
|
213
246
|
config_file = nthlayer_dir / "config.yaml"
|
|
214
|
-
if not config_file.
|
|
247
|
+
if not config_file.is_file():
|
|
215
248
|
config_content = _generate_config_yaml()
|
|
216
249
|
try:
|
|
217
|
-
config_file.write_text(config_content)
|
|
218
|
-
except OSError as e:
|
|
250
|
+
config_file.write_text(config_content, encoding="utf-8")
|
|
251
|
+
except (OSError, UnicodeError) as e:
|
|
219
252
|
warning(f"Could not create config file: {e}")
|
|
220
253
|
|
|
221
254
|
# Success message
|
|
222
255
|
console.print()
|
|
223
256
|
success(f"Created {service_file}")
|
|
224
|
-
|
|
257
|
+
# is_dir(), NOT exists() (opensrm-t4rd): with .nthlayer a regular FILE,
|
|
258
|
+
# exists() is True *because it is a file*, so init claimed to have created
|
|
259
|
+
# a directory it had not.
|
|
260
|
+
if nthlayer_dir.is_dir():
|
|
225
261
|
success(f"Created {nthlayer_dir}/")
|
|
262
|
+
else:
|
|
263
|
+
# NOT fatal, including under --no-interactive. Decided deliberately
|
|
264
|
+
# (opensrm-t4rd acceptance): the manifest is the primary artifact and it
|
|
265
|
+
# was written correctly, so a CI gate keying on the exit code is right to
|
|
266
|
+
# pass. What was wrong before was claiming the directory had been created;
|
|
267
|
+
# the warning below states plainly that it was not, and names the fix.
|
|
268
|
+
# Making it fatal would fail runs whose manifest is perfectly good merely
|
|
269
|
+
# because .nthlayer could not be created (read-only parent, a stray file).
|
|
270
|
+
warning(
|
|
271
|
+
f"{nthlayer_dir}/ was not created — config was not written. "
|
|
272
|
+
f"It already exists as a file, or the parent is not writable."
|
|
273
|
+
)
|
|
226
274
|
|
|
227
275
|
console.print()
|
|
228
276
|
console.print("[bold]Next steps:[/bold]")
|
|
@@ -241,28 +289,147 @@ def init_command(
|
|
|
241
289
|
return 0
|
|
242
290
|
|
|
243
291
|
|
|
244
|
-
|
|
245
|
-
|
|
292
|
+
# WHY THE SCALAR HELPERS BELOW EXIST (opensrm-t4rd).
|
|
293
|
+
#
|
|
294
|
+
# generate's hard rule 5 — no raw string construction for generated output —
|
|
295
|
+
# applied at the one input that is scriptable and unvalidated. `--team` was
|
|
296
|
+
# interpolated raw with only a truthiness check. Measured before the fix, all
|
|
297
|
+
# exiting 0:
|
|
298
|
+
#
|
|
299
|
+
# --team 'Platform: Core' -> the manifest failed to parse
|
|
300
|
+
# --team $'ops\nname: hijacked' -> injected a second service.name
|
|
301
|
+
# --team $'ops\ntier: critical' -> manifest VALIDATED CLEAN carrying a
|
|
302
|
+
# bogus tier shadowed by the real one
|
|
303
|
+
#
|
|
304
|
+
# A colon in a team name is ordinary, so quoting has to be available. It is
|
|
305
|
+
# applied conditionally, for the reason in `_yaml_scalar`, and done with
|
|
306
|
+
# pyyaml rather than `json.dumps`, for the reason in `_quoted_yaml_scalar`.
|
|
307
|
+
#
|
|
308
|
+
# The allowlist is deliberately narrow: anything outside it is quoted rather
|
|
309
|
+
# than reasoned about. It excludes every YAML indicator character (`:` `#`
|
|
310
|
+
# `-` at the start, `{` `[` `&` `*` `!` `|` `>` `%` `@` and a backtick),
|
|
311
|
+
# leading whitespace, and anything non-ASCII. Matching it is necessary but NOT
|
|
312
|
+
# sufficient — see `_emits_as_same_string`.
|
|
313
|
+
_PLAIN_SCALAR_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9 ._-]*")
|
|
246
314
|
|
|
247
|
-
Args:
|
|
248
|
-
name: Service name to validate
|
|
249
315
|
|
|
250
|
-
|
|
251
|
-
|
|
316
|
+
def _yaml_scalar(value: object) -> str:
|
|
317
|
+
"""Render *value* as a YAML scalar that cannot alter the document.
|
|
318
|
+
|
|
319
|
+
Plain when it provably round-trips, quoted otherwise. Quoting is
|
|
320
|
+
conditional so the documented example output stays byte-identical to what
|
|
321
|
+
init writes (``test_block_is_byte_identical_to_real_output``, the
|
|
322
|
+
docs-vs-reality guard from opensrm-noc6).
|
|
323
|
+
|
|
324
|
+
A non-str is quoted rather than rejected: values arrive straight from a
|
|
325
|
+
YAML loader, so a template declaring ``name: on`` makes this a bool and
|
|
326
|
+
``re.fullmatch`` would raise on it.
|
|
252
327
|
"""
|
|
253
|
-
if not
|
|
328
|
+
if not isinstance(value, str):
|
|
329
|
+
return _quoted_yaml_scalar(str(value))
|
|
330
|
+
if _PLAIN_SCALAR_RE.fullmatch(value) and _emits_as_same_string(value):
|
|
331
|
+
return value
|
|
332
|
+
return _quoted_yaml_scalar(value)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
# pyyaml wraps long scalars by default, which would emit a second line into a
|
|
336
|
+
# document built by string interpolation. Large enough to never wrap.
|
|
337
|
+
_NO_WRAP = 10**9
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _quoted_yaml_scalar(value: str) -> str:
|
|
341
|
+
"""Render *value* as a double-quoted YAML scalar on exactly one line.
|
|
342
|
+
|
|
343
|
+
``default_style='"'`` forces the quoted form for every input, so the result
|
|
344
|
+
is always a single line and never a block scalar.
|
|
345
|
+
|
|
346
|
+
``json.dumps`` was used here first, on the reasoning that a JSON string is
|
|
347
|
+
also a valid YAML double-quoted scalar. True for the BMP, false above it:
|
|
348
|
+
with ``ensure_ascii=True`` json escapes a non-BMP character as a UTF-16
|
|
349
|
+
surrogate PAIR, and pyyaml resolves each 16-bit escape separately without
|
|
350
|
+
recombining them, so an emoji team name read back as two lone surrogates,
|
|
351
|
+
validated clean, and could not be re-encoded to UTF-8 at all.
|
|
352
|
+
``ensure_ascii=False`` is no better: it mismatches on U+0085 and makes
|
|
353
|
+
pyyaml raise on U+007F.
|
|
354
|
+
|
|
355
|
+
``allow_unicode=True`` is readability only, not correctness: it emits BMP
|
|
356
|
+
non-ASCII literally (``cafe`` with its accent, rather than an escape).
|
|
357
|
+
Above the BMP pyyaml escapes regardless of the flag -- but as ONE 32-bit
|
|
358
|
+
escape, which its own reader resolves back to one character. That single
|
|
359
|
+
escape, not the flag, is what json's surrogate pair got wrong. Measured:
|
|
360
|
+
both settings round-trip every value in the corpus.
|
|
361
|
+
|
|
362
|
+
The trailing newline pyyaml adds is stripped. No document-end marker is
|
|
363
|
+
stripped, because ``default_style='"'`` means only pyyaml's
|
|
364
|
+
double-quoted writer ever runs, and that writer never emits one.
|
|
365
|
+
"""
|
|
366
|
+
return yaml.safe_dump(value, default_style='"', allow_unicode=True, width=_NO_WRAP).rstrip("\n")
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _emits_as_same_string(value: str) -> bool:
|
|
370
|
+
"""True if *value* written as a plain YAML scalar reads back identically.
|
|
371
|
+
|
|
372
|
+
The allowlist excludes every YAML *indicator*, but says nothing about
|
|
373
|
+
implicit *type resolution*, which is the half that was missing. Measured,
|
|
374
|
+
every one of these matched the allowlist, was emitted unquoted, and loaded
|
|
375
|
+
back as something other than the string written -- all at exit 0:
|
|
376
|
+
|
|
377
|
+
--team null -> None -> validate FAILS, "team is required"
|
|
378
|
+
--team yes/on/true -> True -> validates CLEAN, bool in a str field
|
|
379
|
+
--team no/off/false -> False -> validate FAILS
|
|
380
|
+
--team 123 -> 123 -> int
|
|
381
|
+
--team 1_000 -> 1000 -> int
|
|
382
|
+
--team 1.5 -> 1.5 -> float
|
|
383
|
+
--team 0x1f -> 31 -> int
|
|
384
|
+
--team 2026-01-01 -> date(...) -> datetime.date, validates CLEAN
|
|
385
|
+
--team 'ops ' -> 'ops' -> trailing space silently stripped
|
|
386
|
+
|
|
387
|
+
Case variants resolve too (``NULL``, ``Yes``), which is why this asks the
|
|
388
|
+
loader instead of carrying a deny list that would have to track the YAML 1.1
|
|
389
|
+
type schema -- and stay correct as pyyaml changes. Both shapes above are the
|
|
390
|
+
bead's own defects one field over: the first writes a manifest generate's
|
|
391
|
+
validator rejects, the second a clean manifest carrying a wrong value.
|
|
392
|
+
|
|
393
|
+
Checking the bare scalar is faithful to the mapping-value position it is
|
|
394
|
+
emitted into, because the allowlist already excludes every character whose
|
|
395
|
+
resolution differs between those two contexts.
|
|
396
|
+
|
|
397
|
+
``ValueError`` is caught alongside ``YAMLError`` because pyyaml's integer
|
|
398
|
+
constructor raises it rather than a YAML error on an allowlist-matching
|
|
399
|
+
input: ``0b_`` reaches ``int("", 2)``. Uncaught, that crashed ``nthlayer
|
|
400
|
+
init`` with a traceback and wrote nothing. Fuzzing every
|
|
401
|
+
allowlist-matching string up to length 4 (10248 of them) reaches exactly
|
|
402
|
+
these two types, ``ValueError`` on 3 inputs -- ``0b_``, ``0x_``, ``0b__``.
|
|
403
|
+
|
|
404
|
+
Either way the answer is the same: a value that will not load is not
|
|
405
|
+
provably a plain string, so it gets quoted.
|
|
406
|
+
"""
|
|
407
|
+
try:
|
|
408
|
+
loaded = yaml.safe_load(value)
|
|
409
|
+
except (yaml.YAMLError, ValueError):
|
|
254
410
|
return False
|
|
411
|
+
return isinstance(loaded, str) and loaded == value
|
|
255
412
|
|
|
256
|
-
# Must be lowercase, numbers, and hyphens only
|
|
257
|
-
# Must not start or end with hyphen
|
|
258
|
-
if name[0] == "-" or name[-1] == "-":
|
|
259
|
-
return False
|
|
260
413
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
414
|
+
def _is_valid_team(team: str) -> bool:
|
|
415
|
+
r"""True if *team* contains no line break, tab or NUL.
|
|
416
|
+
|
|
417
|
+
Those four only, not every control character: the rest are quoted correctly
|
|
418
|
+
by ``_yaml_scalar`` and are merely odd, not dangerous. Blankness is not
|
|
419
|
+
checked here either — the caller rejects it first, with a message that
|
|
420
|
+
actually fits it.
|
|
421
|
+
|
|
422
|
+
Quoting alone already makes the document safe, so this is not what prevents
|
|
423
|
+
injection. It is what turns a newline in --team into a clear error rather
|
|
424
|
+
than a silently escaped ``\n`` in the output, because a team name spanning
|
|
425
|
+
lines is a mistake every time.
|
|
426
|
+
"""
|
|
427
|
+
return not any(ch in team for ch in "\n\r\t\x00")
|
|
428
|
+
|
|
264
429
|
|
|
265
|
-
|
|
430
|
+
def _is_valid_service_name(name: str) -> bool:
|
|
431
|
+
"""Delegates to the one service-name rule; see specs/manifest for why."""
|
|
432
|
+
return is_valid_service_name(name)
|
|
266
433
|
|
|
267
434
|
|
|
268
435
|
def _generate_service_yaml_v2(
|
|
@@ -293,14 +460,14 @@ def _generate_service_yaml_v2(
|
|
|
293
460
|
resources_yaml = _build_resources_yaml(service_name, tier, service_type, dependencies)
|
|
294
461
|
|
|
295
462
|
# Template line if using a template
|
|
296
|
-
template_line = f" template: {template.name}\n" if template else ""
|
|
463
|
+
template_line = f" template: {_yaml_scalar(template.name)}\n" if template else ""
|
|
297
464
|
|
|
298
465
|
return f"""# {service_name} Service Definition
|
|
299
466
|
# Generated by NthLayer
|
|
300
467
|
|
|
301
468
|
service:
|
|
302
|
-
name: {service_name}
|
|
303
|
-
team: {team}
|
|
469
|
+
name: {_yaml_scalar(service_name)}
|
|
470
|
+
team: {_yaml_scalar(team)}
|
|
304
471
|
tier: {tier}
|
|
305
472
|
type: {service_type}
|
|
306
473
|
{template_line}
|
|
@@ -457,11 +624,11 @@ def _generate_service_yaml(service_name: str, team: str, template) -> str:
|
|
|
457
624
|
# Generated by NthLayer
|
|
458
625
|
|
|
459
626
|
service:
|
|
460
|
-
name: {service_name}
|
|
461
|
-
team: {team}
|
|
627
|
+
name: {_yaml_scalar(service_name)}
|
|
628
|
+
team: {_yaml_scalar(team)}
|
|
462
629
|
tier: {template.tier} # critical | standard | low
|
|
463
630
|
type: {template.type} # api | worker | stream | batch | database | ai-gate | x-web
|
|
464
|
-
template: {template.name}
|
|
631
|
+
template: {_yaml_scalar(template.name)}
|
|
465
632
|
|
|
466
633
|
# Template provides:
|
|
467
634
|
{_format_template_resources(template)}
|
|
@@ -493,7 +660,12 @@ def _format_template_resources(template) -> str:
|
|
|
493
660
|
"""
|
|
494
661
|
lines = []
|
|
495
662
|
for resource in template.resources:
|
|
496
|
-
|
|
663
|
+
# A comment, but still a line in the document: `resource.kind` and
|
|
664
|
+
# `resource.name` come from a template file on disk with no
|
|
665
|
+
# validation, so a newline in either would escape the `#` and land
|
|
666
|
+
# at top level. `_yaml_scalar` escapes it instead, and leaves the
|
|
667
|
+
# ordinary values (`SLO`, `availability`) unquoted.
|
|
668
|
+
lines.append(f"# - {_yaml_scalar(resource.kind)}: {_yaml_scalar(resource.name)}")
|
|
497
669
|
return "\n".join(lines) if lines else "# (no resources)"
|
|
498
670
|
|
|
499
671
|
|
|
@@ -17,6 +17,12 @@ import argparse
|
|
|
17
17
|
import os
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
|
|
20
|
+
# Reused from init rather than copied, which is this bead's whole point
|
|
21
|
+
# (opensrm-h9fq). It is the only private cross-module import in src/, and
|
|
22
|
+
# relocating these helpers to a shared home is tracked in opensrm-d5ce --
|
|
23
|
+
# deferred because opensrm-t4rd's AST guards match them by name in cli/init.py
|
|
24
|
+
# and that move needs its own verification.
|
|
25
|
+
from nthlayer_generate.cli.init import _is_valid_team, _yaml_scalar
|
|
20
26
|
from nthlayer_generate.cli.ux import (
|
|
21
27
|
confirm,
|
|
22
28
|
console,
|
|
@@ -42,6 +48,7 @@ from nthlayer_generate.config.loader import (
|
|
|
42
48
|
save_config,
|
|
43
49
|
)
|
|
44
50
|
from nthlayer_generate.config.secrets import get_secret_resolver
|
|
51
|
+
from nthlayer_generate.specs.manifest import is_valid_service_name
|
|
45
52
|
|
|
46
53
|
|
|
47
54
|
def setup_command(
|
|
@@ -88,11 +95,23 @@ def setup_command(
|
|
|
88
95
|
print()
|
|
89
96
|
_test_connections()
|
|
90
97
|
|
|
91
|
-
# Offer to create first service
|
|
98
|
+
# Offer to create first service.
|
|
99
|
+
#
|
|
100
|
+
# The return value is PROPAGATED (opensrm-h9fq). It used to be discarded,
|
|
101
|
+
# so every failure inside printed `error(...)` and then this function
|
|
102
|
+
# printed "Setup Complete!" and returned 0. The configuration above IS
|
|
103
|
+
# saved by this point, so the message says what actually happened rather
|
|
104
|
+
# than claiming either total success or total failure.
|
|
105
|
+
service_rc = 0
|
|
92
106
|
if not skip_service:
|
|
93
107
|
print()
|
|
94
108
|
if _confirm("Create your first service?", default=True):
|
|
95
|
-
_create_first_service()
|
|
109
|
+
service_rc = _create_first_service()
|
|
110
|
+
|
|
111
|
+
if service_rc != 0:
|
|
112
|
+
console.print()
|
|
113
|
+
warning("Configuration was saved, but the first service was not created.")
|
|
114
|
+
return service_rc
|
|
96
115
|
|
|
97
116
|
_print_next_steps()
|
|
98
117
|
return 0
|
|
@@ -375,8 +394,36 @@ def _test_pagerduty(config: IntegrationConfig) -> tuple[bool, str]:
|
|
|
375
394
|
return False, str(e)
|
|
376
395
|
|
|
377
396
|
|
|
378
|
-
|
|
379
|
-
|
|
397
|
+
# The wizard's three tier tables, lifted to module level so a test can assert
|
|
398
|
+
# they AGREE (opensrm-h9fq). While they were function-locals, adding a value to
|
|
399
|
+
# `tier_map` with no matching config was an undetectable drift -- the provenance
|
|
400
|
+
# pass mutated exactly that and the suite stayed green.
|
|
401
|
+
TIER_CHOICES = [
|
|
402
|
+
"critical - 99.95% availability, 5min escalation",
|
|
403
|
+
"standard - 99.9% availability, 15min escalation",
|
|
404
|
+
"low - 99.5% availability, 30min escalation",
|
|
405
|
+
]
|
|
406
|
+
TIER_MAP = {"critical": 1, "standard": 2, "low": 3}
|
|
407
|
+
TIER_CONFIGS_BY_INDEX = {
|
|
408
|
+
1: {"availability": 99.95, "latency_ms": 200, "tier_name": "critical"},
|
|
409
|
+
2: {"availability": 99.9, "latency_ms": 500, "tier_name": "standard"},
|
|
410
|
+
3: {"availability": 99.5, "latency_ms": 1000, "tier_name": "low"},
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _create_first_service() -> int:
|
|
415
|
+
"""Guide user through creating their first service.
|
|
416
|
+
|
|
417
|
+
Returns 0 on success OR on a deliberate skip, and 1 on failure, so the
|
|
418
|
+
caller can key an exit code on it (opensrm-h9fq). It returned None while
|
|
419
|
+
every failure path printed `error(...)` and the command then printed
|
|
420
|
+
"Setup Complete!" and exited 0 -- which is this bead's own defect in its
|
|
421
|
+
exit-code half, and `specs/manifest.py` says plainly that an exit code is
|
|
422
|
+
what a CI gate keys on.
|
|
423
|
+
|
|
424
|
+
A user declining is NOT a failure: choosing not to create a service, or
|
|
425
|
+
declining an overwrite, returns 0.
|
|
426
|
+
"""
|
|
380
427
|
console.print()
|
|
381
428
|
console.print("[bold]Create Your First Service[/bold]")
|
|
382
429
|
console.print("[muted]─[/muted]" * 40)
|
|
@@ -385,16 +432,29 @@ def _create_first_service() -> None:
|
|
|
385
432
|
service_name = _prompt("Service name (e.g., payment-api)")
|
|
386
433
|
if not service_name:
|
|
387
434
|
info("Skipping service creation.")
|
|
388
|
-
return
|
|
435
|
+
return 0
|
|
389
436
|
|
|
390
437
|
# Validate name
|
|
391
438
|
if not _is_valid_service_name(service_name):
|
|
392
439
|
error(f"Invalid service name: {service_name}")
|
|
393
|
-
console.print("Use lowercase letters, numbers
|
|
394
|
-
return
|
|
440
|
+
console.print(" [muted]Use lowercase letters, numbers and hyphens only[/muted]")
|
|
441
|
+
return 1
|
|
395
442
|
|
|
396
443
|
team = _prompt("Team name", default="platform")
|
|
397
444
|
|
|
445
|
+
# Gated and quoted for the same reason as init (opensrm-t4rd, opensrm-h9fq):
|
|
446
|
+
# `team` is free text and was interpolated raw, so a colon broke the document
|
|
447
|
+
# and a newline injected a sibling field that validated clean. `_yaml_scalar`
|
|
448
|
+
# makes the document safe; this check turns a newline into a clear error
|
|
449
|
+
# rather than a silently escaped one.
|
|
450
|
+
if not team or not team.strip():
|
|
451
|
+
error("Team name is required")
|
|
452
|
+
return 1
|
|
453
|
+
if not _is_valid_team(team):
|
|
454
|
+
error("Invalid team name")
|
|
455
|
+
console.print(" [muted]Team name must not contain line breaks, tabs or NUL[/muted]")
|
|
456
|
+
return 1
|
|
457
|
+
|
|
398
458
|
# Service type selection using interactive menu
|
|
399
459
|
type_choices = [
|
|
400
460
|
"api - HTTP/REST API service",
|
|
@@ -405,32 +465,98 @@ def _create_first_service() -> None:
|
|
|
405
465
|
service_type = selected_type.split(" - ")[0]
|
|
406
466
|
|
|
407
467
|
# Service tier selection using interactive menu
|
|
408
|
-
tier_choices =
|
|
409
|
-
"critical - 99.95% availability, 5min escalation",
|
|
410
|
-
"standard - 99.9% availability, 15min escalation",
|
|
411
|
-
"low - 99.5% availability, 30min escalation",
|
|
412
|
-
]
|
|
468
|
+
tier_choices = list(TIER_CHOICES)
|
|
413
469
|
selected_tier = select("Service tier", tier_choices, default=tier_choices[1])
|
|
414
470
|
tier_name = selected_tier.split(" - ")[0]
|
|
415
|
-
tier_map =
|
|
471
|
+
tier_map = dict(TIER_MAP)
|
|
416
472
|
tier = tier_map.get(tier_name, 2)
|
|
417
473
|
|
|
418
|
-
#
|
|
474
|
+
# All three of the following mirror cli/init.py's hardening from
|
|
475
|
+
# opensrm-t4rd (opensrm-h9fq). This bead imported init's quoting HELPERS and
|
|
476
|
+
# left the file IO beside them unhardened, so t4rd's defects were still live
|
|
477
|
+
# here verbatim.
|
|
478
|
+
#
|
|
479
|
+
# `exist_ok=True` does not cover a non-directory last component, so a
|
|
480
|
+
# `services` FILE or dangling symlink raised FileExistsError, and a
|
|
481
|
+
# read-only cwd raised PermissionError -- a traceback where init gives a
|
|
482
|
+
# message. Not fatal, as in init: the wizard has nothing else to write.
|
|
419
483
|
services_dir = Path("services")
|
|
420
|
-
|
|
484
|
+
try:
|
|
485
|
+
services_dir.mkdir(exist_ok=True)
|
|
486
|
+
except OSError as e:
|
|
487
|
+
error(f"Could not create {services_dir}/: {e}")
|
|
488
|
+
console.print(
|
|
489
|
+
" [muted]It may already exist as a file, or the parent may not be "
|
|
490
|
+
"writable. Remove or rename it and re-run.[/muted]"
|
|
491
|
+
)
|
|
492
|
+
return 1
|
|
421
493
|
|
|
422
494
|
# Generate service YAML
|
|
423
495
|
service_content = _generate_service_yaml(service_name, team, service_type, tier)
|
|
424
496
|
|
|
497
|
+
# `is_file()`, NOT `exists()`. With a DIRECTORY at the target, `exists()` was
|
|
498
|
+
# True, so the user was asked to confirm an overwrite and then got
|
|
499
|
+
# IsADirectoryError. The same confusion t4rd corrected in init.
|
|
425
500
|
service_file = services_dir / f"{service_name}.yaml"
|
|
426
|
-
if service_file.
|
|
501
|
+
if service_file.is_file():
|
|
427
502
|
if not _confirm(f"{service_file} exists. Overwrite?", default=False):
|
|
428
503
|
info("Skipping service creation.")
|
|
429
|
-
return
|
|
430
|
-
|
|
431
|
-
|
|
504
|
+
return 0
|
|
505
|
+
elif service_file.exists():
|
|
506
|
+
error(f"{service_file} exists but is not a regular file")
|
|
507
|
+
console.print(" [muted]Remove it, or choose a different service name.[/muted]")
|
|
508
|
+
return 1
|
|
509
|
+
|
|
510
|
+
# ENCODE BEFORE OPENING ANYTHING. `write_text` opens with "w", which
|
|
511
|
+
# truncates immediately, so an encoding failure lands AFTER the file is
|
|
512
|
+
# already empty. Measured: a 38-byte manifest went to 0 bytes and the user
|
|
513
|
+
# was told it had been left unchanged. Encoding first moves the whole
|
|
514
|
+
# UnicodeError class in front of any file descriptor, so on that path
|
|
515
|
+
# nothing is touched and saying so is true.
|
|
516
|
+
#
|
|
517
|
+
# DEFENSIVE ONLY: no known wizard input reaches this handler, and the
|
|
518
|
+
# provenance pass confirmed it cannot be kill-checked. Kept because a field
|
|
519
|
+
# added later without `_yaml_scalar` would make it reachable (opensrm-h9fq).
|
|
520
|
+
try:
|
|
521
|
+
payload = service_content.encode("utf-8")
|
|
522
|
+
except UnicodeError as e:
|
|
523
|
+
error(f"Could not encode the manifest: {e}")
|
|
524
|
+
info(f"{service_file} was not touched.")
|
|
525
|
+
return 1
|
|
526
|
+
|
|
527
|
+
# `is_symlink()` as well as `is_file()`. A symlink whose victim is missing
|
|
528
|
+
# is neither a file nor `exists()`, so without this the rollback below
|
|
529
|
+
# deleted a symlink this run did not create -- the same mistake as deleting
|
|
530
|
+
# a pre-existing regular file, one shape over.
|
|
531
|
+
#
|
|
532
|
+
pre_existing = service_file.is_file() or service_file.is_symlink()
|
|
533
|
+
try:
|
|
534
|
+
service_file.write_bytes(payload)
|
|
535
|
+
except OSError as e:
|
|
536
|
+
error(f"Error creating {service_file}: {e}")
|
|
537
|
+
if pre_existing:
|
|
538
|
+
# Deliberately not "unchanged": an OSError at open leaves it intact,
|
|
539
|
+
# but one raised mid-write leaves it partial, and this cannot tell
|
|
540
|
+
# them apart. Atomic replacement is opensrm-may6's scope; until then
|
|
541
|
+
# the message must not promise more than it knows.
|
|
542
|
+
info(
|
|
543
|
+
f"{service_file} was not removed, but may be incomplete. "
|
|
544
|
+
f"Check it with `nthlayer validate {service_file}`, or re-run "
|
|
545
|
+
f"`nthlayer setup`."
|
|
546
|
+
)
|
|
547
|
+
else:
|
|
548
|
+
try:
|
|
549
|
+
service_file.unlink(missing_ok=True)
|
|
550
|
+
except OSError:
|
|
551
|
+
# intentionally ignored: the write already failed and the
|
|
552
|
+
# manifest is the only artifact, so a failed cleanup has
|
|
553
|
+
# nothing left to report and must not mask the real error
|
|
554
|
+
# (hard rule 4).
|
|
555
|
+
pass
|
|
556
|
+
return 1
|
|
432
557
|
console.print()
|
|
433
558
|
success(f"Created {service_file}")
|
|
559
|
+
return 0
|
|
434
560
|
|
|
435
561
|
|
|
436
562
|
def _generate_service_yaml(
|
|
@@ -441,20 +567,24 @@ def _generate_service_yaml(
|
|
|
441
567
|
) -> str:
|
|
442
568
|
"""Generate service YAML content."""
|
|
443
569
|
# Tier-based defaults
|
|
444
|
-
tier_configs =
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
570
|
+
tier_configs = TIER_CONFIGS_BY_INDEX
|
|
571
|
+
# Raise rather than defaulting (opensrm-h9fq). The hazard is a STRING tier:
|
|
572
|
+
# init's sibling `_generate_service_yaml_v2` takes `tier: str`, so now that
|
|
573
|
+
# the emitted field is a name, a caller passing the name would otherwise get
|
|
574
|
+
# a wrong-but-valid manifest. Unreachable today, since `tier_map` clamps the
|
|
575
|
+
# menu to 1-3, so this is a loud guard on a latent silent-wrong rather than
|
|
576
|
+
# a live fix. The cases are enumerated in TestUnknownTierIsLoud.
|
|
577
|
+
if tier not in tier_configs:
|
|
578
|
+
raise ValueError(f"Unknown tier {tier!r}; expected one of {sorted(tier_configs)}")
|
|
579
|
+
config = tier_configs[tier]
|
|
450
580
|
|
|
451
581
|
return f"""# {name} Service Definition
|
|
452
582
|
# Generated by NthLayer setup wizard
|
|
453
583
|
|
|
454
584
|
service:
|
|
455
|
-
name: {name}
|
|
456
|
-
team: {team}
|
|
457
|
-
tier: {
|
|
585
|
+
name: {_yaml_scalar(name)}
|
|
586
|
+
team: {_yaml_scalar(team)}
|
|
587
|
+
tier: {config["tier_name"]}
|
|
458
588
|
type: {service_type}
|
|
459
589
|
|
|
460
590
|
resources:
|
|
@@ -462,7 +592,7 @@ resources:
|
|
|
462
592
|
- kind: SLO
|
|
463
593
|
name: availability
|
|
464
594
|
spec:
|
|
465
|
-
objective: {config[
|
|
595
|
+
objective: {config["availability"]}
|
|
466
596
|
window: 30d
|
|
467
597
|
indicator:
|
|
468
598
|
type: availability
|
|
@@ -476,7 +606,7 @@ resources:
|
|
|
476
606
|
spec:
|
|
477
607
|
objective: 99.0
|
|
478
608
|
window: 30d
|
|
479
|
-
threshold_ms: {config[
|
|
609
|
+
threshold_ms: {config["latency_ms"]}
|
|
480
610
|
indicator:
|
|
481
611
|
type: latency
|
|
482
612
|
percentile: 99
|
|
@@ -503,15 +633,11 @@ resources:
|
|
|
503
633
|
|
|
504
634
|
|
|
505
635
|
def _is_valid_service_name(name: str) -> bool:
|
|
506
|
-
"""
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
for char in name:
|
|
512
|
-
if not (char.islower() or char.isdigit() or char == "-"):
|
|
513
|
-
return False
|
|
514
|
-
return True
|
|
636
|
+
"""Delegates to the one service-name rule; see specs/manifest for why.
|
|
637
|
+
|
|
638
|
+
This was the third private copy (opensrm-h9fq).
|
|
639
|
+
"""
|
|
640
|
+
return is_valid_service_name(name)
|
|
515
641
|
|
|
516
642
|
|
|
517
643
|
def _print_next_steps() -> None:
|