cloudfall 0.2.1__tar.gz → 0.3.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.
- {cloudfall-0.2.1 → cloudfall-0.3.0}/CHANGELOG.md +19 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/PKG-INFO +8 -9
- {cloudfall-0.2.1 → cloudfall-0.3.0}/README.md +7 -8
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/render-migration-guide.md +1 -1
- {cloudfall-0.2.1 → cloudfall-0.3.0}/pyproject.toml +1 -1
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/cli.py +2 -30
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/project.py +26 -260
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_agent_tools.py +3 -4
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_authoring.py +3 -4
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_project.py +44 -176
- {cloudfall-0.2.1 → cloudfall-0.3.0}/uv.lock +1 -1
- {cloudfall-0.2.1 → cloudfall-0.3.0}/.ansible-lint +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/.github/workflows/ci.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/.github/workflows/release.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/.gitignore +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/ARCHITECTURE.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/CONTRIBUTING.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/LICENSE +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/MANIFESTO.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/ROADMAP.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/SECURITY.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/Taskfile.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/README.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/alert-rules/postgresql-down.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/applications/crm.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/components/crm-backend.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/domains/crm-site.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/logging-stacks/operations.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/operator-policies/production.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/server-types/debian-application.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/servers/h1.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/servers/h2.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/services/postgresql-main.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/services/redis-cache.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/examples/ssh-public-keys/example-admin.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/alert-rule.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/application.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/artifact.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/backup-receipt.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/common.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/component.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/deployment-receipt.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/domain.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/environment-receipt.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/logging-stack.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/observed-domain.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/observed-server.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/operator-policy.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/operator-proposal.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/release-receipt.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/server-type.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/server.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/service.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/schemas/v1/ssh-public-key.schema.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/tests/invalid/missing-server/applications/crm.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/tests/invalid/missing-server/components/crm-backend.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/tests/invalid/missing-server/server-types/debian-application.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/tests/invalid/missing-server/servers/h1.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/tests/invalid/schema/components/component-unknown-field.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/tests/observed/compliant/h1.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/config/tests/observed/compliant/h2.json +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/ansible-context.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/availability-design.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/brownfield-design.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/decisions/2026-09-14-no-ansible-replacement.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/decisions/2026-09-14-no-nixos-base.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/decisions/2026-09-14-no-rust-rewrite.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/fleet-goals.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/hybrid-storage-design.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/landscape.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/logging-service-guide.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/neon-migration-guide.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/new-server-storage-guide.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/operator-guide.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/proving-runs/2026-09-08-debian-container-m1.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/proving-runs/2026-09-08-dns-cutover.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/proving-runs/2026-09-08-guided-data-migration.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/proving-runs/2026-09-08-hetzner-m1.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/proving-runs/2026-09-08-hetzner-m2-m3.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/proving-runs/2026-09-08-hetzner-m4-m5.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/proving-runs/2026-09-08-render-cutover.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/proving-runs/2026-09-10-hetzner-m7.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/proving-runs/2026-09-10-hetzner-m8.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/proving-runs/2026-09-10-hetzner-m9.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/proving-runs/2026-09-11-hetzner-m10.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/docs/secrets-guide.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/README.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/ansible.cfg +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/backup.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/baseline.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/bootstrap.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/data.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/deploy.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/domains.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/health.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/inspect.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/logging.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/restart.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/rollback.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/services.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/playbooks/time.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_access/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_access/handlers/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_access/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_access/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_access/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_bootstrap/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_bootstrap/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_bootstrap/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_bootstrap/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_data_migration/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_data_migration/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_data_migration/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_data_migration/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_deploy/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_deploy/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_deploy/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_deploy/tasks/health.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_deploy/tasks/health_gate.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_deploy/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_deploy/tasks/restart.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_deploy/tasks/rollback.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_deploy/templates/component.service.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_firewall/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_firewall/handlers/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_firewall/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_firewall/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_firewall/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_firewall/templates/nftables.conf.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_inspect/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_inspect/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_inspect/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_inspect/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/handlers/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/templates/alertmanager-defaults.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/templates/alertmanager.yaml.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/templates/grafana.ini.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/templates/loki-datasource.yml.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/templates/loki-systemd-override.conf.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/templates/loki.yaml.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/templates/nginx-loki-gateway.conf.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-datasource.yml.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-defaults.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus-rules.yaml.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_backend/templates/prometheus.yaml.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_collector/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_collector/handlers/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_collector/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_collector/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_collector/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_collector/templates/alloy-systemd-override.conf.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_logging_collector/templates/config.alloy.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_nginx_site/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_nginx_site/handlers/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_nginx_site/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_nginx_site/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_nginx_site/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_nginx_site/templates/domain-site.conf.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/handlers/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/templates/backup.service.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/templates/backup.sh.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/templates/backup.timer.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/templates/cloudfall.conf.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.service.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.sh.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_postgresql/templates/restore-check.timer.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/handlers/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/templates/backup.service.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/templates/backup.sh.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/templates/backup.timer.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/templates/cloudfall.conf.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/templates/restore-check.service.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/templates/restore-check.sh.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_redis/templates/restore-check.timer.j2 +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_time/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_time/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_time/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_time/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_unattended_upgrades/defaults/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_unattended_upgrades/meta/argument_specs.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_unattended_upgrades/meta/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/ansible/roles/cloudfall_unattended_upgrades/tasks/main.yml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/src/cloudfall_engine/__init__.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/src/cloudfall_engine/__main__.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/src/cloudfall_engine/ansible_inventory.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/src/cloudfall_engine/artifact.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/src/cloudfall_engine/cli.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/src/cloudfall_engine/playbook.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/src/cloudfall_engine/py.typed +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/tests/test_ansible_inventory.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/tests/test_artifact_builder.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/tests/test_deploy_templates.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/tests/test_firewall_templates.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/tests/test_logging_templates.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/tests/test_nginx_site_templates.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/tests/test_playbook.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/engine/tests/test_postgresql_templates.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/README.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/environment.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/findings.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/issues.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/linkedin.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/readiness.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/report-agent-dev.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/report-dev.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/report-index.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/report-issues.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/report-runtime.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/trace.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/evaluations/cloudfall/x.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/examples/render-demo/README.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/examples/render-demo/pyproject.toml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/examples/render-demo/render.yaml +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/examples/render-demo/src/demo/__init__.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/examples/render-demo/src/demo/asgi.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/examples/render-demo/uv.lock +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/README.md +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/__init__.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/__main__.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/agent_tools.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/arguments.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/audit.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/authoring.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/commands.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/cutover.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/dashboard.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/dashboard_server.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/domain.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/importer.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/inventory.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/lifecycle.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/mcp_server.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/migrate.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/observation.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/operations.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/operator.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/py.typed +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/render_api.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/resources.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/secrets.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/service_evidence.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/src/cloudfall/validation.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_arguments.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_audit.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_commands.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_cutover.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_dashboard_server.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_data_migration.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_importer.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_inventory.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_lifecycle.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_migrate.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_operations_dashboard.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_operator.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_render_api.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_resources.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_secrets.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_service_evidence.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/sdk/tests/test_validation.py +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/site/public/apple-touch-icon.png +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/site/public/favicon-32.png +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/site/public/favicon.svg +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/site/public/icon-192.png +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/site/public/icon-512.png +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/site/public/index.html +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/site/public/manifesto.html +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/site/public/og.png +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/site/public/site.webmanifest +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/site/src/worker.js +0 -0
- {cloudfall-0.2.1 → cloudfall-0.3.0}/site/wrangler.jsonc +0 -0
|
@@ -6,6 +6,25 @@ Notable changes to Cloudfall. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.0] — 2026-09-20
|
|
10
|
+
|
|
11
|
+
### Removed
|
|
12
|
+
|
|
13
|
+
- `cloudfall init --rev` and `--source`, and the machinery behind them:
|
|
14
|
+
`GitRevision`, `GitSourceUrl`, `GitPin`, `IndexPin`, `CheckoutState`, the
|
|
15
|
+
checkout inspection, and the `project_revision_unresolved`,
|
|
16
|
+
`project_revision_uncommitted` and `project_revision_unpublished` errors.
|
|
17
|
+
Cloudfall is on PyPI, so a project pins a release and resolves it like any
|
|
18
|
+
other dependency; `resolve_installed_version` replaces
|
|
19
|
+
`resolve_installed_pin`, and the `init` envelope reports `version` rather
|
|
20
|
+
than a `pin` object. To run an unreleased Cloudfall in a project, point the
|
|
21
|
+
dependency at a checkout with uv's own `[tool.uv.sources]`
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- The quickstart and the Render migration guide install Cloudfall from the
|
|
26
|
+
index rather than from git
|
|
27
|
+
|
|
9
28
|
## [0.2.1] — 2026-09-20
|
|
10
29
|
|
|
11
30
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cloudfall
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: AI-native control plane for dedicated Debian application fleets
|
|
5
5
|
Project-URL: Homepage, https://cloudfall.dev
|
|
6
6
|
Project-URL: Repository, https://github.com/romamo/cloudfall
|
|
@@ -198,10 +198,8 @@ Your servers, applications, evidence, and any playbooks of your own belong in
|
|
|
198
198
|
a project: a directory of its own, kept private. Cloudfall's wheel bundles the
|
|
199
199
|
schema catalog and the Ansible engine, so the project needs no checkout of
|
|
200
200
|
this repository. `cloudfall init` is the first command, and it pins the
|
|
201
|
-
project to the Cloudfall that ran it
|
|
202
|
-
|
|
203
|
-
directory argument it initializes the current directory, which must be
|
|
204
|
-
empty:
|
|
201
|
+
project to the released Cloudfall that ran it. Without a directory argument
|
|
202
|
+
it initializes the current directory, which must be empty:
|
|
205
203
|
|
|
206
204
|
```console
|
|
207
205
|
uvx cloudfall init my-project
|
|
@@ -230,10 +228,11 @@ dependencies = ["cloudfall==<version>"]
|
|
|
230
228
|
package = false
|
|
231
229
|
```
|
|
232
230
|
|
|
233
|
-
Bump that
|
|
234
|
-
|
|
235
|
-
a
|
|
236
|
-
|
|
231
|
+
Bump that version and run `uv sync` to move the project to a newer
|
|
232
|
+
Cloudfall. To run an unreleased Cloudfall in a project, point the dependency
|
|
233
|
+
at a checkout or a commit with uv's own
|
|
234
|
+
[`[tool.uv.sources]`](https://docs.astral.sh/uv/concepts/projects/dependencies/#dependency-sources);
|
|
235
|
+
Cloudfall itself only ever pins a release.
|
|
237
236
|
|
|
238
237
|
Only the kind directories are read as resources, so playbooks, roles, docs,
|
|
239
238
|
and tooling files may live anywhere else in the project. Commands find the
|
|
@@ -172,10 +172,8 @@ Your servers, applications, evidence, and any playbooks of your own belong in
|
|
|
172
172
|
a project: a directory of its own, kept private. Cloudfall's wheel bundles the
|
|
173
173
|
schema catalog and the Ansible engine, so the project needs no checkout of
|
|
174
174
|
this repository. `cloudfall init` is the first command, and it pins the
|
|
175
|
-
project to the Cloudfall that ran it
|
|
176
|
-
|
|
177
|
-
directory argument it initializes the current directory, which must be
|
|
178
|
-
empty:
|
|
175
|
+
project to the released Cloudfall that ran it. Without a directory argument
|
|
176
|
+
it initializes the current directory, which must be empty:
|
|
179
177
|
|
|
180
178
|
```console
|
|
181
179
|
uvx cloudfall init my-project
|
|
@@ -204,10 +202,11 @@ dependencies = ["cloudfall==<version>"]
|
|
|
204
202
|
package = false
|
|
205
203
|
```
|
|
206
204
|
|
|
207
|
-
Bump that
|
|
208
|
-
|
|
209
|
-
a
|
|
210
|
-
|
|
205
|
+
Bump that version and run `uv sync` to move the project to a newer
|
|
206
|
+
Cloudfall. To run an unreleased Cloudfall in a project, point the dependency
|
|
207
|
+
at a checkout or a commit with uv's own
|
|
208
|
+
[`[tool.uv.sources]`](https://docs.astral.sh/uv/concepts/projects/dependencies/#dependency-sources);
|
|
209
|
+
Cloudfall itself only ever pins a release.
|
|
211
210
|
|
|
212
211
|
Only the kind directories are read as resources, so playbooks, roles, docs,
|
|
213
212
|
and tooling files may live anywhere else in the project. Commands find the
|
|
@@ -55,7 +55,7 @@ Not imported today; each lands in the gap report instead of being guessed at:
|
|
|
55
55
|
- Your `render.yaml` blueprint and access to your application repositories
|
|
56
56
|
- Python 3.14 and [`uv`](https://docs.astral.sh/uv/) on the machine you run
|
|
57
57
|
Cloudfall from (your workstation works; no management server is required)
|
|
58
|
-
- A project: `uvx
|
|
58
|
+
- A project: `uvx cloudfall init my-project`,
|
|
59
59
|
then `cd my-project && uv sync`. Every command below runs from inside it
|
|
60
60
|
|
|
61
61
|
## Step 1: import the blueprint
|
|
@@ -96,18 +96,14 @@ from cloudfall.operator import (
|
|
|
96
96
|
run_once as operator_run_once,
|
|
97
97
|
)
|
|
98
98
|
from cloudfall.project import (
|
|
99
|
-
DEFAULT_SOURCE_URL,
|
|
100
99
|
PROJECT_DIRECTORY_VARIABLE,
|
|
101
|
-
GitPin,
|
|
102
|
-
GitRevision,
|
|
103
|
-
GitSourceUrl,
|
|
104
100
|
InitOptions,
|
|
105
101
|
ProjectDescription,
|
|
106
102
|
ProjectError,
|
|
107
103
|
ProjectName,
|
|
108
104
|
init_project,
|
|
109
105
|
project_context,
|
|
110
|
-
|
|
106
|
+
resolve_installed_version,
|
|
111
107
|
)
|
|
112
108
|
from cloudfall.render_api import (
|
|
113
109
|
HttpRenderApiClient,
|
|
@@ -200,14 +196,6 @@ def _add_init_parser(
|
|
|
200
196
|
"--name",
|
|
201
197
|
help="package name written to pyproject.toml (default: the directory name)",
|
|
202
198
|
)
|
|
203
|
-
init_parser.add_argument(
|
|
204
|
-
"--rev",
|
|
205
|
-
help=(
|
|
206
|
-
"Cloudfall commit to pin, installed from --source (default: the "
|
|
207
|
-
"released version of the running cloudfall, or the commit it was "
|
|
208
|
-
"installed from when that was a git or source install)"
|
|
209
|
-
),
|
|
210
|
-
)
|
|
211
199
|
init_parser.add_argument(
|
|
212
200
|
"--description",
|
|
213
201
|
help=(
|
|
@@ -215,14 +203,6 @@ def _add_init_parser(
|
|
|
215
203
|
"and pyproject.toml"
|
|
216
204
|
),
|
|
217
205
|
)
|
|
218
|
-
init_parser.add_argument(
|
|
219
|
-
"--source",
|
|
220
|
-
default=DEFAULT_SOURCE_URL,
|
|
221
|
-
help=(
|
|
222
|
-
"git location of Cloudfall to install from, with --rev "
|
|
223
|
-
f"(default: {DEFAULT_SOURCE_URL})"
|
|
224
|
-
),
|
|
225
|
-
)
|
|
226
206
|
|
|
227
207
|
|
|
228
208
|
def _add_add_parsers(
|
|
@@ -1033,18 +1013,10 @@ def _run_init(arguments: Namespace) -> int:
|
|
|
1033
1013
|
if arguments.name is not None
|
|
1034
1014
|
else ProjectName.from_directory(directory)
|
|
1035
1015
|
)
|
|
1036
|
-
pin = (
|
|
1037
|
-
GitPin(
|
|
1038
|
-
GitRevision.from_boundary(arguments.rev),
|
|
1039
|
-
GitSourceUrl.from_boundary(arguments.source),
|
|
1040
|
-
)
|
|
1041
|
-
if arguments.rev is not None
|
|
1042
|
-
else resolve_installed_pin()
|
|
1043
|
-
)
|
|
1044
1016
|
options = InitOptions(
|
|
1045
1017
|
directory=directory,
|
|
1046
1018
|
name=name,
|
|
1047
|
-
|
|
1019
|
+
version=resolve_installed_version(),
|
|
1048
1020
|
description=(
|
|
1049
1021
|
ProjectDescription.from_boundary(arguments.description)
|
|
1050
1022
|
if arguments.description is not None
|
|
@@ -4,8 +4,7 @@ A project is one directory holding everything Cloudfall needs to run one
|
|
|
4
4
|
user's stack: the fleet (servers, server types, SSH public keys), the
|
|
5
5
|
applications (applications, components, services, domains), the operating
|
|
6
6
|
declarations (alert rules, operator policies, logging stacks), and the
|
|
7
|
-
``pyproject.toml`` that pins the Cloudfall the project runs with
|
|
8
|
-
released version from the package index, or a commit in the repository.
|
|
7
|
+
``pyproject.toml`` that pins the released Cloudfall the project runs with.
|
|
9
8
|
Every ``cloudfall`` command runs inside a project: the current directory
|
|
10
9
|
when it is one, else ``--project``, else ``CLOUDFALL_PROJECT``.
|
|
11
10
|
Evidence, receipts, and rendered files land under ``tmp/`` inside the
|
|
@@ -14,7 +13,6 @@ project and are never committed.
|
|
|
14
13
|
|
|
15
14
|
from __future__ import annotations
|
|
16
15
|
|
|
17
|
-
import json
|
|
18
16
|
import os
|
|
19
17
|
import re
|
|
20
18
|
import shutil
|
|
@@ -26,7 +24,6 @@ from enum import Enum
|
|
|
26
24
|
from importlib import metadata
|
|
27
25
|
from pathlib import Path
|
|
28
26
|
from typing import TYPE_CHECKING
|
|
29
|
-
from urllib.parse import urlparse
|
|
30
27
|
|
|
31
28
|
from cloudfall.commands import CommandEffect, commands_with_effect
|
|
32
29
|
from cloudfall.domain import ResourceKind
|
|
@@ -34,20 +31,17 @@ from cloudfall.domain import ResourceKind
|
|
|
34
31
|
if TYPE_CHECKING:
|
|
35
32
|
from collections.abc import Callable, Iterator, Mapping
|
|
36
33
|
|
|
37
|
-
_GIT_REVISION_PATTERN = re.compile(r"^[0-9a-f]{40}$")
|
|
38
34
|
_PROJECT_NAME_PATTERN = re.compile(r"^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$")
|
|
39
35
|
_RELEASE_VERSION_PATTERN = re.compile(
|
|
40
36
|
r"^\d+(?:\.\d+)*(?:(?:a|b|rc)\d+)?(?:\.post\d+)?(?:\.dev\d+)?$"
|
|
41
37
|
)
|
|
42
|
-
_SCP_LIKE_SOURCE_PATTERN = re.compile(
|
|
43
|
-
r"^(?P<user>[\w.-]+)@(?P<host>[\w.-]+):(?P<path>[\w./-]+)$"
|
|
44
|
-
)
|
|
45
38
|
_PROJECT_NAME_MAX_LENGTH = 64
|
|
46
39
|
_PROJECT_DESCRIPTION_MAX_LENGTH = 512
|
|
47
40
|
_GIT_TIMEOUT_SECONDS = 30
|
|
48
41
|
_GIT_NOT_A_REPOSITORY_EXIT_CODE = 128
|
|
49
42
|
|
|
50
|
-
|
|
43
|
+
REPOSITORY_URL = "https://github.com/romamo/cloudfall"
|
|
44
|
+
"""Location of the Cloudfall repository, for links to its files."""
|
|
51
45
|
|
|
52
46
|
SECRETS_DIRECTORY = "secrets"
|
|
53
47
|
"""Project directory holding sops-encrypted secret fragments."""
|
|
@@ -70,9 +64,6 @@ CLAUDE_POINTER_FILE = "CLAUDE.md"
|
|
|
70
64
|
_ERROR_NAME_UNDERIVED = "project_name_underived"
|
|
71
65
|
_ERROR_DIRECTORY_MISSING = "project_directory_missing"
|
|
72
66
|
_ERROR_DIRECTORY_UNRESOLVED = "project_directory_unresolved"
|
|
73
|
-
_ERROR_REVISION_UNRESOLVED = "project_revision_unresolved"
|
|
74
|
-
_ERROR_REVISION_UNCOMMITTED = "project_revision_uncommitted"
|
|
75
|
-
_ERROR_REVISION_UNPUBLISHED = "project_revision_unpublished"
|
|
76
67
|
_ERROR_VERSION_UNRESOLVED = "project_version_unresolved"
|
|
77
68
|
_ERROR_NOT_A_DIRECTORY = "project_directory_not_a_directory"
|
|
78
69
|
_ERROR_NOT_EMPTY = "project_directory_not_empty"
|
|
@@ -104,76 +95,6 @@ class ProjectError(RuntimeError):
|
|
|
104
95
|
}
|
|
105
96
|
|
|
106
97
|
|
|
107
|
-
@dataclass(frozen=True, slots=True)
|
|
108
|
-
class GitRevision:
|
|
109
|
-
"""Full 40-character commit hash a project pins Cloudfall to."""
|
|
110
|
-
|
|
111
|
-
value: str
|
|
112
|
-
|
|
113
|
-
def __post_init__(self) -> None:
|
|
114
|
-
"""Enforce the full-hash invariant at construction time."""
|
|
115
|
-
if not _GIT_REVISION_PATTERN.fullmatch(self.value):
|
|
116
|
-
message = f"git revision must be a full 40-hex commit hash: {self.value!r}"
|
|
117
|
-
raise ValueError(message)
|
|
118
|
-
|
|
119
|
-
@classmethod
|
|
120
|
-
def from_boundary(cls, value: object) -> GitRevision:
|
|
121
|
-
"""Coerce a boundary value while keeping internal APIs strictly typed."""
|
|
122
|
-
if not isinstance(value, str):
|
|
123
|
-
message = f"git revision must be a string, got {type(value).__name__}"
|
|
124
|
-
raise TypeError(message)
|
|
125
|
-
return cls(value.strip().lower())
|
|
126
|
-
|
|
127
|
-
def __str__(self) -> str:
|
|
128
|
-
"""Return the serialized hash."""
|
|
129
|
-
return self.value
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
@dataclass(frozen=True, slots=True)
|
|
133
|
-
class GitSourceUrl:
|
|
134
|
-
"""Location of the Cloudfall repository a project installs from."""
|
|
135
|
-
|
|
136
|
-
value: str
|
|
137
|
-
|
|
138
|
-
def __post_init__(self) -> None:
|
|
139
|
-
"""Accept https and ssh git locations only."""
|
|
140
|
-
parsed = urlparse(self.value)
|
|
141
|
-
https = parsed.scheme == "https" and bool(parsed.netloc)
|
|
142
|
-
ssh = parsed.scheme == "ssh" and bool(parsed.netloc)
|
|
143
|
-
scp_like = _SCP_LIKE_SOURCE_PATTERN.fullmatch(self.value) is not None
|
|
144
|
-
if not (https or ssh or scp_like):
|
|
145
|
-
message = f"git source must be an https or ssh location: {self.value!r}"
|
|
146
|
-
raise ValueError(message)
|
|
147
|
-
|
|
148
|
-
@classmethod
|
|
149
|
-
def from_boundary(cls, value: object) -> GitSourceUrl:
|
|
150
|
-
"""Coerce a boundary value while keeping internal APIs strictly typed."""
|
|
151
|
-
if not isinstance(value, str):
|
|
152
|
-
message = f"git source must be a string, got {type(value).__name__}"
|
|
153
|
-
raise TypeError(message)
|
|
154
|
-
return cls(value.strip())
|
|
155
|
-
|
|
156
|
-
@property
|
|
157
|
-
def browse_url(self) -> str:
|
|
158
|
-
"""Return the https location of the repository for links to its files.
|
|
159
|
-
|
|
160
|
-
``https://host/owner/repo.git``, ``ssh://git@host/owner/repo.git``, and
|
|
161
|
-
``git@host:owner/repo.git`` all browse at ``https://host/owner/repo``.
|
|
162
|
-
"""
|
|
163
|
-
scp_like = _SCP_LIKE_SOURCE_PATTERN.fullmatch(self.value)
|
|
164
|
-
if scp_like is not None:
|
|
165
|
-
host, path = scp_like.group("host"), scp_like.group("path")
|
|
166
|
-
else:
|
|
167
|
-
parsed = urlparse(self.value)
|
|
168
|
-
host, path = parsed.hostname or "", parsed.path
|
|
169
|
-
path = path.strip("/").removesuffix(".git")
|
|
170
|
-
return f"https://{host}/{path}"
|
|
171
|
-
|
|
172
|
-
def __str__(self) -> str:
|
|
173
|
-
"""Return the serialized location."""
|
|
174
|
-
return self.value
|
|
175
|
-
|
|
176
|
-
|
|
177
98
|
@dataclass(frozen=True, slots=True)
|
|
178
99
|
class ReleaseVersion:
|
|
179
100
|
"""Released Cloudfall version a project pins from the package index."""
|
|
@@ -199,84 +120,6 @@ class ReleaseVersion:
|
|
|
199
120
|
return self.value
|
|
200
121
|
|
|
201
122
|
|
|
202
|
-
@dataclass(frozen=True, slots=True)
|
|
203
|
-
class GitPin:
|
|
204
|
-
"""A project that installs Cloudfall from a commit in the repository."""
|
|
205
|
-
|
|
206
|
-
revision: GitRevision
|
|
207
|
-
source: GitSourceUrl = GitSourceUrl(DEFAULT_SOURCE_URL)
|
|
208
|
-
|
|
209
|
-
@property
|
|
210
|
-
def requirement(self) -> str:
|
|
211
|
-
"""Return the dependency specifier for the project's ``pyproject``."""
|
|
212
|
-
return "cloudfall"
|
|
213
|
-
|
|
214
|
-
@property
|
|
215
|
-
def sources_table(self) -> str:
|
|
216
|
-
"""Return the ``[tool.uv.sources]`` table that resolves the commit."""
|
|
217
|
-
return f"""
|
|
218
|
-
# Cloudfall is an installed package: the schema catalog and the Ansible engine
|
|
219
|
-
# ship inside the wheel. Bump `rev` to move this project to a newer Cloudfall
|
|
220
|
-
# commit, then run `uv sync`. Add the `mcp` extra (`cloudfall[mcp]`) to run
|
|
221
|
-
# the `cloudfall-mcp` agent server.
|
|
222
|
-
[tool.uv.sources]
|
|
223
|
-
cloudfall = {{ git = "{self.source}", rev = "{self.revision}" }}
|
|
224
|
-
"""
|
|
225
|
-
|
|
226
|
-
@property
|
|
227
|
-
def browse_url(self) -> str:
|
|
228
|
-
"""Return the https location of the repository the pin reads from."""
|
|
229
|
-
return self.source.browse_url
|
|
230
|
-
|
|
231
|
-
@property
|
|
232
|
-
def browse_ref(self) -> str:
|
|
233
|
-
"""Return the git ref whose files match the pinned Cloudfall."""
|
|
234
|
-
return str(self.revision)
|
|
235
|
-
|
|
236
|
-
def as_dict(self) -> dict[str, object]:
|
|
237
|
-
"""Serialize the pin for system boundaries."""
|
|
238
|
-
return {
|
|
239
|
-
"kind": "git",
|
|
240
|
-
"revision": str(self.revision),
|
|
241
|
-
"source": str(self.source),
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
@dataclass(frozen=True, slots=True)
|
|
246
|
-
class IndexPin:
|
|
247
|
-
"""A project that installs a released Cloudfall from the package index."""
|
|
248
|
-
|
|
249
|
-
version: ReleaseVersion
|
|
250
|
-
|
|
251
|
-
@property
|
|
252
|
-
def requirement(self) -> str:
|
|
253
|
-
"""Return the dependency specifier for the project's ``pyproject``."""
|
|
254
|
-
return f"cloudfall=={self.version}"
|
|
255
|
-
|
|
256
|
-
@property
|
|
257
|
-
def sources_table(self) -> str:
|
|
258
|
-
"""Return nothing: a released version needs no source override."""
|
|
259
|
-
return ""
|
|
260
|
-
|
|
261
|
-
@property
|
|
262
|
-
def browse_url(self) -> str:
|
|
263
|
-
"""Return the https location of the repository the release came from."""
|
|
264
|
-
return GitSourceUrl(DEFAULT_SOURCE_URL).browse_url
|
|
265
|
-
|
|
266
|
-
@property
|
|
267
|
-
def browse_ref(self) -> str:
|
|
268
|
-
"""Return the release tag whose files match the pinned Cloudfall."""
|
|
269
|
-
return f"v{self.version}"
|
|
270
|
-
|
|
271
|
-
def as_dict(self) -> dict[str, object]:
|
|
272
|
-
"""Serialize the pin for system boundaries."""
|
|
273
|
-
return {"kind": "index", "version": str(self.version)}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
type ProjectPin = GitPin | IndexPin
|
|
277
|
-
"""What a project pins Cloudfall to: a released version, or a commit."""
|
|
278
|
-
|
|
279
|
-
|
|
280
123
|
@dataclass(frozen=True, slots=True)
|
|
281
124
|
class ProjectName:
|
|
282
125
|
"""Package name of a project, as written into its ``pyproject.toml``."""
|
|
@@ -363,7 +206,7 @@ class InitOptions:
|
|
|
363
206
|
|
|
364
207
|
directory: Path
|
|
365
208
|
name: ProjectName
|
|
366
|
-
|
|
209
|
+
version: ReleaseVersion
|
|
367
210
|
description: ProjectDescription | None = None
|
|
368
211
|
|
|
369
212
|
|
|
@@ -383,7 +226,7 @@ class ProjectScaffold:
|
|
|
383
226
|
|
|
384
227
|
directory: Path
|
|
385
228
|
name: ProjectName
|
|
386
|
-
|
|
229
|
+
version: ReleaseVersion
|
|
387
230
|
files: tuple[str, ...]
|
|
388
231
|
git: GitSetup
|
|
389
232
|
|
|
@@ -394,7 +237,7 @@ class ProjectScaffold:
|
|
|
394
237
|
"project": {
|
|
395
238
|
"directory": str(self.directory),
|
|
396
239
|
"name": str(self.name),
|
|
397
|
-
"
|
|
240
|
+
"version": str(self.version),
|
|
398
241
|
"git": self.git.value,
|
|
399
242
|
},
|
|
400
243
|
"files": list(self.files),
|
|
@@ -407,17 +250,6 @@ class ProjectScaffold:
|
|
|
407
250
|
}
|
|
408
251
|
|
|
409
252
|
|
|
410
|
-
@dataclass(frozen=True, slots=True)
|
|
411
|
-
class CheckoutState:
|
|
412
|
-
"""What a source checkout says about the commit it is running."""
|
|
413
|
-
|
|
414
|
-
head: str
|
|
415
|
-
clean: bool
|
|
416
|
-
"""No tracked file differs from ``HEAD``, so ``HEAD`` is the running code."""
|
|
417
|
-
published: bool
|
|
418
|
-
"""``HEAD`` is reachable from a remote-tracking branch, so it can be fetched."""
|
|
419
|
-
|
|
420
|
-
|
|
421
253
|
def is_project(directory: Path) -> bool:
|
|
422
254
|
"""Return whether a directory holds at least one resource kind directory."""
|
|
423
255
|
return directory.is_dir() and any(
|
|
@@ -493,77 +325,24 @@ def project_path(value: str) -> Path:
|
|
|
493
325
|
return path
|
|
494
326
|
|
|
495
327
|
|
|
496
|
-
def
|
|
497
|
-
read_direct_url: Callable[[], str | None] | None = None,
|
|
498
|
-
inspect_checkout: Callable[[Path], CheckoutState] | None = None,
|
|
499
|
-
read_version: Callable[[], str] | None = None,
|
|
500
|
-
) -> ProjectPin:
|
|
501
|
-
"""Return what the running Cloudfall pins a new project to.
|
|
502
|
-
|
|
503
|
-
An install from the package index records no origin, and its version is
|
|
504
|
-
the pin: the project depends on ``cloudfall==<version>`` and resolves it
|
|
505
|
-
the ordinary way. A wheel installed from git records its commit in
|
|
506
|
-
``direct_url.json``; an editable source checkout records the checkout
|
|
507
|
-
location, whose ``HEAD`` is the commit, provided the checkout is clean
|
|
508
|
-
(else ``HEAD`` is not the code that is running) and ``HEAD`` is on a
|
|
509
|
-
remote branch (else ``uv sync`` cannot fetch it). Anything else cannot be
|
|
510
|
-
pinned and must be given explicitly.
|
|
511
|
-
"""
|
|
512
|
-
direct_url = (read_direct_url or _installed_direct_url)()
|
|
513
|
-
if direct_url is None:
|
|
514
|
-
return IndexPin(_installed_release_version(read_version))
|
|
515
|
-
record = json.loads(direct_url)
|
|
516
|
-
if not isinstance(record, dict):
|
|
517
|
-
message = f"direct_url.json is not an object: {direct_url!r}"
|
|
518
|
-
raise ProjectError(_ERROR_REVISION_UNRESOLVED, message)
|
|
519
|
-
vcs_info = record.get("vcs_info")
|
|
520
|
-
if isinstance(vcs_info, dict) and "commit_id" in vcs_info:
|
|
521
|
-
return GitPin(GitRevision.from_boundary(vcs_info["commit_id"]))
|
|
522
|
-
dir_info = record.get("dir_info")
|
|
523
|
-
url = record.get("url")
|
|
524
|
-
if (
|
|
525
|
-
isinstance(dir_info, dict)
|
|
526
|
-
and isinstance(url, str)
|
|
527
|
-
and url.startswith("file://")
|
|
528
|
-
):
|
|
529
|
-
checkout = Path(urlparse(url).path)
|
|
530
|
-
inspect = inspect_checkout or _inspect_checkout
|
|
531
|
-
return GitPin(_pinnable_head(checkout, inspect(checkout)))
|
|
532
|
-
message = (
|
|
533
|
-
"the installed cloudfall distribution is neither a git install nor a "
|
|
534
|
-
f"source checkout ({url!r}); pass --rev with the commit to pin"
|
|
535
|
-
)
|
|
536
|
-
raise ProjectError(_ERROR_REVISION_UNRESOLVED, message)
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
def _pinnable_head(checkout: Path, state: CheckoutState) -> GitRevision:
|
|
540
|
-
head = GitRevision.from_boundary(state.head)
|
|
541
|
-
if not state.clean:
|
|
542
|
-
message = (
|
|
543
|
-
f"the source checkout {checkout} has uncommitted changes, so HEAD "
|
|
544
|
-
f"{head} is not the code that is running; commit them or pass --rev"
|
|
545
|
-
)
|
|
546
|
-
raise ProjectError(_ERROR_REVISION_UNCOMMITTED, message)
|
|
547
|
-
if not state.published:
|
|
548
|
-
message = (
|
|
549
|
-
f"HEAD {head} of the source checkout {checkout} is on no remote "
|
|
550
|
-
"branch, so `uv sync` could not fetch it; push it or pass --rev"
|
|
551
|
-
)
|
|
552
|
-
raise ProjectError(_ERROR_REVISION_UNPUBLISHED, message)
|
|
553
|
-
return head
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
def _installed_release_version(
|
|
328
|
+
def resolve_installed_version(
|
|
557
329
|
read_version: Callable[[], str] | None = None,
|
|
558
330
|
) -> ReleaseVersion:
|
|
331
|
+
"""Return the released version of the running Cloudfall.
|
|
332
|
+
|
|
333
|
+
A project depends on ``cloudfall==<version>`` and resolves it from the
|
|
334
|
+
package index like any other dependency. An install whose version is not
|
|
335
|
+
a release cannot be pinned that way, which is a checkout of this
|
|
336
|
+
repository between releases.
|
|
337
|
+
"""
|
|
559
338
|
raw = (read_version or _installed_version)()
|
|
560
339
|
try:
|
|
561
340
|
return ReleaseVersion.from_boundary(raw)
|
|
562
341
|
except ValueError as error:
|
|
563
342
|
message = (
|
|
564
|
-
"the installed cloudfall
|
|
565
|
-
|
|
566
|
-
"
|
|
343
|
+
f"the installed cloudfall version {raw!r} is not a release, so a "
|
|
344
|
+
"project cannot pin it; install a released cloudfall, or set the "
|
|
345
|
+
"dependency in the project's pyproject.toml by hand"
|
|
567
346
|
)
|
|
568
347
|
raise ProjectError(_ERROR_VERSION_UNRESOLVED, message) from error
|
|
569
348
|
|
|
@@ -572,10 +351,6 @@ def _installed_version() -> str:
|
|
|
572
351
|
return metadata.version("cloudfall")
|
|
573
352
|
|
|
574
353
|
|
|
575
|
-
def _installed_direct_url() -> str | None:
|
|
576
|
-
return metadata.distribution("cloudfall").read_text("direct_url.json")
|
|
577
|
-
|
|
578
|
-
|
|
579
354
|
def _git_binary() -> str:
|
|
580
355
|
git = shutil.which("git")
|
|
581
356
|
if git is None:
|
|
@@ -609,15 +384,6 @@ def _git_or_fail(directory: Path, *arguments: str) -> str:
|
|
|
609
384
|
return completed.stdout
|
|
610
385
|
|
|
611
386
|
|
|
612
|
-
def _inspect_checkout(checkout: Path) -> CheckoutState:
|
|
613
|
-
head = _git_or_fail(checkout, "rev-parse", "HEAD").strip()
|
|
614
|
-
changes = _git_or_fail(checkout, "status", "--porcelain", "--untracked-files=no")
|
|
615
|
-
remote_branches = _git_or_fail(checkout, "branch", "--remotes", "--contains", head)
|
|
616
|
-
return CheckoutState(
|
|
617
|
-
head=head, clean=not changes.strip(), published=bool(remote_branches.strip())
|
|
618
|
-
)
|
|
619
|
-
|
|
620
|
-
|
|
621
387
|
def _initialize_git(directory: Path) -> GitSetup:
|
|
622
388
|
"""Create a repository in the directory unless one already encloses it."""
|
|
623
389
|
enclosing = _run_git(directory, "rev-parse", "--is-inside-work-tree")
|
|
@@ -666,7 +432,7 @@ def init_project(
|
|
|
666
432
|
return ProjectScaffold(
|
|
667
433
|
directory=directory,
|
|
668
434
|
name=options.name,
|
|
669
|
-
|
|
435
|
+
version=options.version,
|
|
670
436
|
files=tuple(files),
|
|
671
437
|
git=git,
|
|
672
438
|
)
|
|
@@ -684,14 +450,14 @@ version = "0"
|
|
|
684
450
|
description = "{description}"
|
|
685
451
|
requires-python = ">=3.14"
|
|
686
452
|
# Cloudfall is an installed package: the schema catalog and the Ansible engine
|
|
687
|
-
# ship inside the wheel. Bump this
|
|
453
|
+
# ship inside the wheel. Bump this version to move the project to a newer
|
|
688
454
|
# Cloudfall, then run `uv sync`. Add the `mcp` extra (`cloudfall[mcp]`) to run
|
|
689
455
|
# the `cloudfall-mcp` agent server.
|
|
690
|
-
dependencies = ["{options.
|
|
456
|
+
dependencies = ["cloudfall=={options.version}"]
|
|
691
457
|
|
|
692
458
|
[tool.uv]
|
|
693
459
|
package = false
|
|
694
|
-
|
|
460
|
+
"""
|
|
695
461
|
|
|
696
462
|
|
|
697
463
|
_GITIGNORE = f"""# Runtime state: evidence, receipts, rendered inventory, built
|
|
@@ -722,8 +488,8 @@ creation_rules:
|
|
|
722
488
|
def _readme(options: InitOptions, git: GitSetup) -> str:
|
|
723
489
|
tmp = RUNTIME_DIRECTORY
|
|
724
490
|
secrets = SECRETS_DIRECTORY
|
|
725
|
-
browse =
|
|
726
|
-
ref = options.
|
|
491
|
+
browse = REPOSITORY_URL
|
|
492
|
+
ref = f"v{options.version}"
|
|
727
493
|
secrets_guide = f"{browse}/blob/{ref}/{SECRETS_GUIDE_PATH}"
|
|
728
494
|
examples = f"{browse}/tree/{ref}/{EXAMPLES_PATH}"
|
|
729
495
|
about = (
|
|
@@ -808,9 +574,9 @@ uv run cloudfall migrate --build <component>=main
|
|
|
808
574
|
uv run cloudfall migrate --build <component>=main --yes
|
|
809
575
|
```
|
|
810
576
|
|
|
811
|
-
Cloudfall is pinned to one
|
|
812
|
-
`uv sync` to move the project to a newer release. The links below
|
|
813
|
-
the pinned
|
|
577
|
+
Cloudfall is pinned to one released version in `pyproject.toml`; bump it
|
|
578
|
+
and run `uv sync` to move the project to a newer release. The links below
|
|
579
|
+
point at the pinned release.
|
|
814
580
|
|
|
815
581
|
[cloudfall]: {browse}
|
|
816
582
|
[secrets-guide]: {secrets_guide}
|
|
@@ -9,10 +9,9 @@ import pytest
|
|
|
9
9
|
import yaml
|
|
10
10
|
from cloudfall.agent_tools import AgentConfig, AgentToolset
|
|
11
11
|
from cloudfall.project import (
|
|
12
|
-
GitPin,
|
|
13
|
-
GitRevision,
|
|
14
12
|
InitOptions,
|
|
15
13
|
ProjectName,
|
|
14
|
+
ReleaseVersion,
|
|
16
15
|
init_project,
|
|
17
16
|
)
|
|
18
17
|
|
|
@@ -32,7 +31,7 @@ services:
|
|
|
32
31
|
"""
|
|
33
32
|
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
VERSION = "0.3.0"
|
|
36
35
|
|
|
37
36
|
|
|
38
37
|
def _config(
|
|
@@ -147,7 +146,7 @@ def _fresh_project(tmp_path: Path) -> AgentToolset:
|
|
|
147
146
|
InitOptions(
|
|
148
147
|
directory=directory,
|
|
149
148
|
name=ProjectName("project"),
|
|
150
|
-
|
|
149
|
+
version=ReleaseVersion(VERSION),
|
|
151
150
|
)
|
|
152
151
|
)
|
|
153
152
|
return AgentToolset(_config(tmp_path, project_directory=directory))
|
|
@@ -18,10 +18,9 @@ from cloudfall.authoring import (
|
|
|
18
18
|
from cloudfall.cli import main
|
|
19
19
|
from cloudfall.domain import ConnectionAddress, LinuxUser, ResourceId, TcpPort
|
|
20
20
|
from cloudfall.project import (
|
|
21
|
-
GitPin,
|
|
22
|
-
GitRevision,
|
|
23
21
|
InitOptions,
|
|
24
22
|
ProjectName,
|
|
23
|
+
ReleaseVersion,
|
|
25
24
|
init_project,
|
|
26
25
|
)
|
|
27
26
|
from cloudfall.validation import ConfigValidationError, validate_config
|
|
@@ -29,7 +28,7 @@ from cloudfall.validation import ConfigValidationError, validate_config
|
|
|
29
28
|
ROOT = Path(__file__).parents[2]
|
|
30
29
|
SCHEMAS = ROOT / "config" / "schemas" / "v1"
|
|
31
30
|
EXAMPLES = ROOT / "config" / "examples"
|
|
32
|
-
|
|
31
|
+
VERSION = "0.3.0"
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
def _example_key() -> str:
|
|
@@ -52,7 +51,7 @@ def _project(tmp_path: Path) -> Path:
|
|
|
52
51
|
InitOptions(
|
|
53
52
|
directory=directory,
|
|
54
53
|
name=ProjectName("project"),
|
|
55
|
-
|
|
54
|
+
version=ReleaseVersion(VERSION),
|
|
56
55
|
)
|
|
57
56
|
)
|
|
58
57
|
return directory
|