diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..a5fba5a
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,17 @@
+# Variables lues par docker-compose.yml a la racine.
+# Le backend lance hors conteneur (`make dev`) lit apps/backend/.env, pas ce fichier.
+
+POSTGRES_USER=enervision
+POSTGRES_PASSWORD=change_me
+POSTGRES_DB=enervision
+# 5432 est souvent deja pris par une autre base du poste.
+POSTGRES_PORT=5433
+# `basic` renvoie des statistiques d'usage a Timescale.
+TIMESCALEDB_TELEMETRY=off
+
+APP_ENV=local
+APP_DEBUG=true
+APP_LOG_LEVEL=INFO
+APP_SECRET_KEY=change_me
+APP_CORS_ORIGINS=http://localhost:4200
+BACKEND_PORT=8000
diff --git a/.gitignore b/.gitignore
index af1ea90..bb3dca3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ venv/
.coverage
coverage.xml
htmlcov/
+test-results/
dist/
build/
*.egg-info/
@@ -23,7 +24,7 @@ yarn-error.log*
# Terraform
.terraform/
-.terraform.lock.hcl
+# .terraform.lock.hcl est versionne (pas ignore) pour figer les versions de provider entre contributeurs/CI
*.tfstate
*.tfstate.*
*.tfplan
@@ -32,6 +33,9 @@ override.tf
override.tf.json
*_override.tf
*_override.tf.json
+*.tfvars
+!*.tfvars.example
+kubeconfig
# Airflow
etl/airflow/logs/
diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl
deleted file mode 100644
index da14477..0000000
--- a/.terraform.lock.hcl
+++ /dev/null
@@ -1,23 +0,0 @@
-# This file is maintained automatically by "terraform init".
-# Manual edits may be lost in future updates.
-
-provider "registry.terraform.io/kreuzwerker/docker" {
- version = "3.9.0"
- constraints = "~> 3.0"
- hashes = [
- "h1:MmhVJBgNpE2Fbksv/XObZJncwm4th4mFE7Ai+6LiIy4=",
- "zh:0ead8281830e9b9496651282235d9a139ba1b1b6ff79e395eb8c78658dc446b9",
- "zh:0f17d37d8d3872df3fb75c68b5272e0c981343f53b506a9675b4405191edd3ef",
- "zh:11d50b37323874427c6d2a08b737d3c7707c8301fdd236c94485cf2828d0b14b",
- "zh:32f6f9b847446054e2db3d72886ef2f1d1aa51a6d0dac42340b07dad18e3f28f",
- "zh:5ea5c67668b5dcbda560dc6104b788a9bfc974d52f02f7886889b77cc0e5d248",
- "zh:5fb19a0b07edc344cd3ddeeb9cfb3d183089deb7a6a94a7b22a583aa1712596b",
- "zh:602a7ece444e2a142ec5245abb98e7a1a990a68afae2df63b6c85ec084f0c5d7",
- "zh:693dce278524ad8a6d6c9dd7a01bcd63bb85189639198f8d0b044ab0e5099401",
- "zh:72e9911568103576c6a78fa38841cfd45eeb88ad22a2c649eb140a377a5b3c26",
- "zh:956b62b6857cbb467b50158601f01b1203daa34cbd447dcc7f044c327e878b68",
- "zh:9d372bac0d4479868b34485fb4966ba7bb525938f818b6a625f4977004ea83f9",
- "zh:e06658a51427f9f53dbdb06263406fc1bc56d1a4fb5e7eb660d7cdfc22f596bd",
- "zh:eee38dadf672b946419af25160eae7c03fc2afbb14f39f2f1d2a7404d647e2f7",
- ]
-}
diff --git a/.terraform/providers/registry.terraform.io/kreuzwerker/docker/3.9.0/windows_386/CHANGELOG.md b/.terraform/providers/registry.terraform.io/kreuzwerker/docker/3.9.0/windows_386/CHANGELOG.md
deleted file mode 100644
index 35b0c05..0000000
--- a/.terraform/providers/registry.terraform.io/kreuzwerker/docker/3.9.0/windows_386/CHANGELOG.md
+++ /dev/null
@@ -1,870 +0,0 @@
-
-
-## [v3.9.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.8.0...v3.9.0) (2025-11-09)
-
-### Chore
-
-* Add file requested by hashicorp ([#813](https://github.com/kreuzwerker/terraform-provider-docker/issues/813))
-* Prepare release v3.8.0 ([#806](https://github.com/kreuzwerker/terraform-provider-docker/issues/806))
-
-### Feat
-
-* Implement caching of docker provider ([#808](https://github.com/kreuzwerker/terraform-provider-docker/issues/808))
-
-### Fix
-
-* test attribute of docker_service healthcheck is not required ([#815](https://github.com/kreuzwerker/terraform-provider-docker/issues/815))
-* docker_service label can be updated without recreate ([#814](https://github.com/kreuzwerker/terraform-provider-docker/issues/814))
-
-
-
-## [v3.8.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.7.0...v3.8.0) (2025-10-08)
-
-### Feat
-
-* Add build attribute for docker_registry_image ([#805](https://github.com/kreuzwerker/terraform-provider-docker/issues/805))
-* Add build option for additional contexts ([#798](https://github.com/kreuzwerker/terraform-provider-docker/issues/798))
-* implement mac_address for networks_advanced ([#794](https://github.com/kreuzwerker/terraform-provider-docker/issues/794))
-* Implement docker cluster volume ([#793](https://github.com/kreuzwerker/terraform-provider-docker/issues/793))
-
-
-
-## [v3.7.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.6.2...v3.7.0) (2025-08-19)
-
-### Chore
-
-* Prepare release v3.7.0 ([#774](https://github.com/kreuzwerker/terraform-provider-docker/issues/774))
-
-### Feat
-
-* Implement memory_reservation and network_mode enhancements ([#773](https://github.com/kreuzwerker/terraform-provider-docker/issues/773))
-* Implement cache_from and cache_to for docker_image ([#772](https://github.com/kreuzwerker/terraform-provider-docker/issues/772))
-
-### Fix
-
-* Correctly get and set nanoCPUs for docker_container ([#771](https://github.com/kreuzwerker/terraform-provider-docker/issues/771))
-
-
-
-## [v3.6.2](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.6.1...v3.6.2) (2025-06-13)
-
-### Chore
-
-* Prepare release v3.6.2 ([#750](https://github.com/kreuzwerker/terraform-provider-docker/issues/750))
-
-### Feat
-
-* Allow digest in image name ([#744](https://github.com/kreuzwerker/terraform-provider-docker/issues/744))
-
-### Fix
-
-* Remove wrong buildkit version assignment ([#747](https://github.com/kreuzwerker/terraform-provider-docker/issues/747))
-* Reading non existant volume should recreate ([#749](https://github.com/kreuzwerker/terraform-provider-docker/issues/749))
-* Typo in cgroup_parent handling ([#746](https://github.com/kreuzwerker/terraform-provider-docker/issues/746))
-
-
-
-## [v3.6.1](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.6.0...v3.6.1) (2025-06-05)
-
-### Chore
-
-* Prepare release v3.6.1 ([#743](https://github.com/kreuzwerker/terraform-provider-docker/issues/743))
-
-### Feat
-
-* allow to set the cgroup parent for container ([#609](https://github.com/kreuzwerker/terraform-provider-docker/issues/609))
-
-
-
-## [v3.6.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.5.0...v3.6.0) (2025-05-25)
-
-### Chore
-
-* Prepare release v3.6.0 ([#735](https://github.com/kreuzwerker/terraform-provider-docker/issues/735))
-
-### Feat
-
-* Implement correct cpu scheduler settings ([#732](https://github.com/kreuzwerker/terraform-provider-docker/issues/732))
-* Add implementaion of capabilities in docker servic ([#727](https://github.com/kreuzwerker/terraform-provider-docker/issues/727))
-* implement Buildx builder resource ([#724](https://github.com/kreuzwerker/terraform-provider-docker/issues/724))
-
-### Fix
-
-* Implement buildx fixes for general buildkit support and platform handling ([#734](https://github.com/kreuzwerker/terraform-provider-docker/issues/734))
-* Make endpoint validation less strict ([#733](https://github.com/kreuzwerker/terraform-provider-docker/issues/733))
-
-
-
-## [v3.5.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.4.0...v3.5.0) (2025-05-06)
-
-### Chore
-
-* Prepare release v3.5.0 ([#721](https://github.com/kreuzwerker/terraform-provider-docker/issues/721))
-
-### Feat
-
-* Implement using of buildx for docker_image ([#717](https://github.com/kreuzwerker/terraform-provider-docker/issues/717))
-* Support registries that return empty auth scope [#646](https://github.com/kreuzwerker/terraform-provider-docker/issues/646)
-* Implement registry_image_manifests data source ([#714](https://github.com/kreuzwerker/terraform-provider-docker/issues/714))
-* Implement healthcheck start interval ([#713](https://github.com/kreuzwerker/terraform-provider-docker/issues/713))
-
-
-
-## [v3.4.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.3.0...v3.4.0) (2025-04-25)
-
-### Chore
-
-* Prepare release v3.4.0 ([#712](https://github.com/kreuzwerker/terraform-provider-docker/issues/712))
-
-### Feat
-
-* Implement volume_options subpath ([#710](https://github.com/kreuzwerker/terraform-provider-docker/issues/710))
-
-### Fix
-
-* Prevent recreation of image name is intentionally set to a fixed value ([#711](https://github.com/kreuzwerker/terraform-provider-docker/issues/711))
-* Improve container wait handling ([#709](https://github.com/kreuzwerker/terraform-provider-docker/issues/709))
-* Use auth_config block also for registry_image delete functionality ([#708](https://github.com/kreuzwerker/terraform-provider-docker/issues/708))
-
-
-
-## [v3.3.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.2.0...v3.3.0) (2025-04-19)
-
-### Chore
-
-* Prepare release v3.3.0 ([#705](https://github.com/kreuzwerker/terraform-provider-docker/issues/705))
-* Update terraform-plugin-sdk/v2 dependency ([#699](https://github.com/kreuzwerker/terraform-provider-docker/issues/699))
-* Update docker/docker and docker/cli to newest stable ([#695](https://github.com/kreuzwerker/terraform-provider-docker/issues/695))
-
-### Feat
-
-* Implement support for docker context ([#704](https://github.com/kreuzwerker/terraform-provider-docker/issues/704))
-* disable_docker_daemon_check for provider ([#703](https://github.com/kreuzwerker/terraform-provider-docker/issues/703))
-* Implement tag triggers for docker_tag resource ([#702](https://github.com/kreuzwerker/terraform-provider-docker/issues/702))
-* Implement auth_config for docker_registry_image ([#701](https://github.com/kreuzwerker/terraform-provider-docker/issues/701))
-
-### Fix
-
-* Store correctly ports from server ([#698](https://github.com/kreuzwerker/terraform-provider-docker/issues/698))
-
-
-
-## [v3.2.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.1.2...v3.2.0) (2025-04-16)
-
-### Chore
-
-* Prepare release v3.2.0 ([#694](https://github.com/kreuzwerker/terraform-provider-docker/issues/694))
-* Upgrade golangci-lint to next major version ([#686](https://github.com/kreuzwerker/terraform-provider-docker/issues/686))
-
-### Docs
-
-* Consolidated update of docs from several PRs ([#691](https://github.com/kreuzwerker/terraform-provider-docker/issues/691))
-
-### Feat
-
-* Implement upload permissions in docker_container resource ([#693](https://github.com/kreuzwerker/terraform-provider-docker/issues/693))
-* Implement docker_image timeouts ([#692](https://github.com/kreuzwerker/terraform-provider-docker/issues/692))
-* Add support for build-secrets ([#604](https://github.com/kreuzwerker/terraform-provider-docker/issues/604))
-
-### Fix
-
-* Authentication to ECR public ([#690](https://github.com/kreuzwerker/terraform-provider-docker/issues/690))
-
-
-
-## [v3.1.2](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.1.1...v3.1.2) (2025-04-15)
-
-### Chore
-
-* prepare release 3.1.2 ([#688](https://github.com/kreuzwerker/terraform-provider-docker/issues/688))
-
-
-
-## [v3.1.1](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.1.0...v3.1.1) (2025-04-14)
-
-### Chore
-
-* Prepare release 3.1.1 ([#687](https://github.com/kreuzwerker/terraform-provider-docker/issues/687))
-
-
-
-## [v3.1.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.0.2...v3.1.0) (2025-04-14)
-
-### Chore
-
-* Prepare release 3.1.0 ([#685](https://github.com/kreuzwerker/terraform-provider-docker/issues/685))
-* update Go version to 1.22 for consistency across workflows, jo… ([#613](https://github.com/kreuzwerker/terraform-provider-docker/issues/613))
-
-### Feat
-
-* support setting cpu shares ([#575](https://github.com/kreuzwerker/terraform-provider-docker/issues/575))
-
-### Fix
-
-* Use build_args everywhere and update documentation ([#681](https://github.com/kreuzwerker/terraform-provider-docker/issues/681))
-* Compress build context before sending it to Docker ([#461](https://github.com/kreuzwerker/terraform-provider-docker/issues/461))
-* Set correct default network driver and fix a test ([#677](https://github.com/kreuzwerker/terraform-provider-docker/issues/677))
-
-### Typo
-
-* s/presend/present/ ([#606](https://github.com/kreuzwerker/terraform-provider-docker/issues/606))
-
-
-
-## [v3.0.2](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.0.1...v3.0.2) (2023-03-17)
-
-### Chore
-
-* Prepare release v3.0.2
-
-### Docs
-
-* correct spelling of "networks_advanced" ([#517](https://github.com/kreuzwerker/terraform-provider-docker/issues/517))
-
-### Fix
-
-* Implement proxy support. ([#529](https://github.com/kreuzwerker/terraform-provider-docker/issues/529))
-
-
-
-## [v3.0.1](https://github.com/kreuzwerker/terraform-provider-docker/compare/v3.0.0...v3.0.1) (2023-01-13)
-
-### Chore
-
-* Prepare release v3.0.1
-
-### Fix
-
-* Access health of container correctly. ([#506](https://github.com/kreuzwerker/terraform-provider-docker/issues/506))
-
-
-
-## [v3.0.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.25.0...v3.0.0) (2023-01-13)
-
-### Chore
-
-* Prepare release v3.0.0
-
-### Docs
-
-* Update documentation.
-* Add migration guide and update README ([#502](https://github.com/kreuzwerker/terraform-provider-docker/issues/502))
-
-### Feat
-
-* Prepare v3 release ([#503](https://github.com/kreuzwerker/terraform-provider-docker/issues/503))
-
-
-
-## [v2.25.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.24.0...v2.25.0) (2023-01-05)
-
-### Chore
-
-* Prepare release v2.25.0
-
-### Docs
-
-* Add documentation of remote hosts. ([#498](https://github.com/kreuzwerker/terraform-provider-docker/issues/498))
-
-### Feat
-
-* Migrate build block to `docker_image` ([#501](https://github.com/kreuzwerker/terraform-provider-docker/issues/501))
-* Add platform attribute to docker_image resource ([#500](https://github.com/kreuzwerker/terraform-provider-docker/issues/500))
-* Add sysctl implementation to container of docker_service. ([#499](https://github.com/kreuzwerker/terraform-provider-docker/issues/499))
-
-
-
-## [v2.24.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.23.1...v2.24.0) (2022-12-23)
-
-### Chore
-
-* Prepare release v2.24.0
-
-### Docs
-
-* Fix generated website.
-* Update command typo ([#487](https://github.com/kreuzwerker/terraform-provider-docker/issues/487))
-
-### Feat
-
-* cgroupns support ([#497](https://github.com/kreuzwerker/terraform-provider-docker/issues/497))
-* Add triggers attribute to docker_registry_image ([#496](https://github.com/kreuzwerker/terraform-provider-docker/issues/496))
-* Support registries with disabled auth ([#494](https://github.com/kreuzwerker/terraform-provider-docker/issues/494))
-* add IPAM options block for docker networks ([#491](https://github.com/kreuzwerker/terraform-provider-docker/issues/491))
-
-### Fix
-
-* Pin data source specific tag test to older tag.
-
-### Tests
-
-* Add test for parsing auth headers.
-
-
-
-## [v2.23.1](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.23.0...v2.23.1) (2022-11-23)
-
-### Chore
-
-* Prepare release v2.23.1
-
-### Fix
-
-* Update shasum of busybox:1.35.0 tag in test.
-* Handle Auth Header Scopes ([#482](https://github.com/kreuzwerker/terraform-provider-docker/issues/482))
-* Set OS_ARCH from GOHOSTOS and GOHOSTARCH ([#477](https://github.com/kreuzwerker/terraform-provider-docker/issues/477))
-
-
-
-## [v2.23.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.22.0...v2.23.0) (2022-11-02)
-
-### Chore
-
-* Prepare release v2.23.0
-
-### Feat
-
-* wait container healthy state ([#467](https://github.com/kreuzwerker/terraform-provider-docker/issues/467))
-* add docker logs data source ([#471](https://github.com/kreuzwerker/terraform-provider-docker/issues/471))
-
-### Fix
-
-* Update shasum of busybox:1.35.0 tag in test.
-* Update shasum of busybox:1.35.0 tag
-* Correct provider name to match the public registry ([#462](https://github.com/kreuzwerker/terraform-provider-docker/issues/462))
-
-
-
-## [v2.22.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.21.0...v2.22.0) (2022-09-20)
-
-### Chore
-
-* Prepare release v2.22.0
-
-### Feat
-
-* Configurable timeout for docker_container resource stateChangeConf ([#454](https://github.com/kreuzwerker/terraform-provider-docker/issues/454))
-
-### Fix
-
-* oauth authorization support for azurecr ([#451](https://github.com/kreuzwerker/terraform-provider-docker/issues/451))
-
-
-
-## [v2.21.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.20.3...v2.21.0) (2022-09-05)
-
-### Chore
-
-* Prepare release v2.21.0
-
-### Docs
-
-* Fix docker config example.
-
-### Feat
-
-* Add image_id attribute to docker_image resource. ([#450](https://github.com/kreuzwerker/terraform-provider-docker/issues/450))
-* Update used goversion to 1.18. ([#449](https://github.com/kreuzwerker/terraform-provider-docker/issues/449))
-
-### Fix
-
-* Replace deprecated .latest attribute with new image_id. ([#453](https://github.com/kreuzwerker/terraform-provider-docker/issues/453))
-* Remove reading part of docker_tag resource. ([#448](https://github.com/kreuzwerker/terraform-provider-docker/issues/448))
-* Fix repo_digest value for DockerImageDatasource test.
-
-
-
-## [v2.20.3](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.20.2...v2.20.3) (2022-08-31)
-
-### Chore
-
-* Prepare release v2.20.3
-
-### Fix
-
-* Docker Registry Image data source use HEAD request to query image digest ([#433](https://github.com/kreuzwerker/terraform-provider-docker/issues/433))
-* Adding Support for Windows Paths in Bash ([#438](https://github.com/kreuzwerker/terraform-provider-docker/issues/438))
-
-
-
-## [v2.20.2](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.20.1...v2.20.2) (2022-08-10)
-
-### Chore
-
-* Prepare release v2.20.2
-
-### Fix
-
-* Check the operating system for determining the default Docker socket ([#427](https://github.com/kreuzwerker/terraform-provider-docker/issues/427))
-
-### Reverts
-
-* fix(deps): update module github.com/golangci/golangci-lint to v1.48.0 ([#423](https://github.com/kreuzwerker/terraform-provider-docker/issues/423))
-
-
-
-## [v2.20.1](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.20.0...v2.20.1) (2022-08-10)
-
-### Chore
-
-* Prepare release v2.20.1
-* Reduce time to setup AccTests ([#430](https://github.com/kreuzwerker/terraform-provider-docker/issues/430))
-
-### Docs
-
-* Improve docker network usage documentation [skip-ci]
-
-### Feat
-
-* Implement triggers attribute for docker_image. ([#425](https://github.com/kreuzwerker/terraform-provider-docker/issues/425))
-
-### Fix
-
-* Add ForceTrue to docker_image name attribute. ([#421](https://github.com/kreuzwerker/terraform-provider-docker/issues/421))
-
-
-
-## [v2.20.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.19.0...v2.20.0) (2022-07-28)
-
-### Chore
-
-* Prepare release v2.20.0
-* Fix release targets in Makefile.
-
-### Feat
-
-* Implementation of `docker_tag` resource. ([#418](https://github.com/kreuzwerker/terraform-provider-docker/issues/418))
-* Implement support for insecure registries ([#414](https://github.com/kreuzwerker/terraform-provider-docker/issues/414))
-
-
-
-## [v2.19.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.18.1...v2.19.0) (2022-07-15)
-
-### Chore
-
-* Prepare release v2.19.0
-
-### Feat
-
-* Add gpu flag to docker_container resource ([#405](https://github.com/kreuzwerker/terraform-provider-docker/issues/405))
-
-### Fix
-
-* Enable authentication to multiple registries again. ([#400](https://github.com/kreuzwerker/terraform-provider-docker/issues/400))
-* ECR authentication ([#409](https://github.com/kreuzwerker/terraform-provider-docker/issues/409))
-
-
-
-## [v2.18.1](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.18.0...v2.18.1) (2022-07-14)
-
-### Chore
-
-* Prepare release v2.18.1
-* Automate changelog generation [skip ci]
-
-### Fix
-
-* Improve searchLocalImages error handling. ([#407](https://github.com/kreuzwerker/terraform-provider-docker/issues/407))
-* Throw errors when any part of docker config file handling goes wrong. ([#406](https://github.com/kreuzwerker/terraform-provider-docker/issues/406))
-* Enables having a Dockerfile outside the context ([#402](https://github.com/kreuzwerker/terraform-provider-docker/issues/402))
-
-
-
-## [v2.18.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.17.0...v2.18.0) (2022-07-11)
-
-### Chore
-
-* prepare release v2.18.0
-
-### Feat
-
-* add runtime, stop_signal and stop_timeout properties to the docker_container resource ([#364](https://github.com/kreuzwerker/terraform-provider-docker/issues/364))
-
-### Fix
-
-* Correctly handle build files and context for docker_registry_image ([#398](https://github.com/kreuzwerker/terraform-provider-docker/issues/398))
-* Switch to proper go tools mechanism to fix website-* workflows. ([#399](https://github.com/kreuzwerker/terraform-provider-docker/issues/399))
-* compare relative paths when excluding, fixes kreuzwerker[#280](https://github.com/kreuzwerker/terraform-provider-docker/issues/280) ([#397](https://github.com/kreuzwerker/terraform-provider-docker/issues/397))
-
-
-
-## [v2.17.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.16.0...v2.17.0) (2022-06-23)
-
-### Chore
-
-* prepare release v2.17.0
-* Exclude examples directory from renovate.
-* remove the workflow to close stale issues and pull requests ([#371](https://github.com/kreuzwerker/terraform-provider-docker/issues/371))
-
-### Fix
-
-* update go package files directly on master to fix build.
-* correct authentication for ghcr.io registry([#349](https://github.com/kreuzwerker/terraform-provider-docker/issues/349))
-
-
-
-## [v2.16.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.15.0...v2.16.0) (2022-01-24)
-
-### Chore
-
-* prepare release v2.16.0
-
-### Docs
-
-* fix service options ([#337](https://github.com/kreuzwerker/terraform-provider-docker/issues/337))
-* update registry_image.md ([#321](https://github.com/kreuzwerker/terraform-provider-docker/issues/321))
-* fix r/registry_image truncated docs ([#304](https://github.com/kreuzwerker/terraform-provider-docker/issues/304))
-
-### Feat
-
-* add parameter for SSH options ([#335](https://github.com/kreuzwerker/terraform-provider-docker/issues/335))
-
-### Fix
-
-* pass container rm flag ([#322](https://github.com/kreuzwerker/terraform-provider-docker/issues/322))
-* add nil check of DriverConfig ([#315](https://github.com/kreuzwerker/terraform-provider-docker/issues/315))
-* fmt of go files for go 1.17
-
-
-
-## [v2.15.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.14.0...v2.15.0) (2021-08-11)
-
-### Chore
-
-* prepare release v2.15.0
-* re go gets terraform-plugin-docs
-
-### Docs
-
-* corrects authentication misspell. Closes [#264](https://github.com/kreuzwerker/terraform-provider-docker/issues/264)
-
-### Feat
-
-* add container storage opts ([#258](https://github.com/kreuzwerker/terraform-provider-docker/issues/258))
-
-### Fix
-
-* add current timestamp for file upload to container ([#259](https://github.com/kreuzwerker/terraform-provider-docker/issues/259))
-
-
-
-## [v2.14.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.13.0...v2.14.0) (2021-07-09)
-
-### Chore
-
-* prepare release v2.14.0
-
-### Docs
-
-* update to absolute path for registry image context ([#246](https://github.com/kreuzwerker/terraform-provider-docker/issues/246))
-* update readme with logos and subsections ([#235](https://github.com/kreuzwerker/terraform-provider-docker/issues/235))
-
-### Feat
-
-* support terraform v1 ([#242](https://github.com/kreuzwerker/terraform-provider-docker/issues/242))
-
-### Fix
-
-* Update the URL of the docker hub registry ([#230](https://github.com/kreuzwerker/terraform-provider-docker/issues/230))
-
-
-
-## [v2.13.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.12.2...v2.13.0) (2021-06-22)
-
-### Chore
-
-* prepare release v2.13.0
-
-### Docs
-
-* fix a few typos ([#216](https://github.com/kreuzwerker/terraform-provider-docker/issues/216))
-* fix typos in docker_image example usage ([#213](https://github.com/kreuzwerker/terraform-provider-docker/issues/213))
-
-
-
-## [v2.12.2](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.12.1...v2.12.2) (2021-05-26)
-
-### Chore
-
-* prepare release v2.12.2
-
-
-
-## [v2.12.1](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.12.0...v2.12.1) (2021-05-26)
-
-### Chore
-
-* update changelog for v2.12.1
-
-### Fix
-
-* add service host flattener with space split ([#205](https://github.com/kreuzwerker/terraform-provider-docker/issues/205))
-* service state upgradeV2 for empty auth
-
-
-
-## [v2.12.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.11.0...v2.12.0) (2021-05-23)
-
-### Chore
-
-* update changelog for v2.12.0
-* ignore dist folder
-* configure actions/stale ([#157](https://github.com/kreuzwerker/terraform-provider-docker/issues/157))
-* add the guide about Terraform Configuration in Bug Report ([#139](https://github.com/kreuzwerker/terraform-provider-docker/issues/139))
-* bump docker dependency to v20.10.5 ([#119](https://github.com/kreuzwerker/terraform-provider-docker/issues/119))
-
-### Ci
-
-* run acceptance tests with multiple Terraform versions ([#129](https://github.com/kreuzwerker/terraform-provider-docker/issues/129))
-
-### Docs
-
-* update for v2.12.0
-* add releasing steps
-* format `Guide of Bug report` ([#159](https://github.com/kreuzwerker/terraform-provider-docker/issues/159))
-* add an example to build an image with docker_image ([#158](https://github.com/kreuzwerker/terraform-provider-docker/issues/158))
-* add a guide about writing issues to CONTRIBUTING.md ([#149](https://github.com/kreuzwerker/terraform-provider-docker/issues/149))
-* fix Github repository URL in README ([#136](https://github.com/kreuzwerker/terraform-provider-docker/issues/136))
-
-### Feat
-
-* support darwin arm builds and golang 1.16 ([#140](https://github.com/kreuzwerker/terraform-provider-docker/issues/140))
-* migrate to terraform-sdk v2 ([#102](https://github.com/kreuzwerker/terraform-provider-docker/issues/102))
-
-### Fix
-
-* rewriting tar header fields ([#198](https://github.com/kreuzwerker/terraform-provider-docker/issues/198))
-* test spaces for windows ([#190](https://github.com/kreuzwerker/terraform-provider-docker/issues/190))
-* replace for loops with StateChangeConf ([#182](https://github.com/kreuzwerker/terraform-provider-docker/issues/182))
-* skip sign on compile action
-* assign map to rawState when it is nil to prevent panic ([#180](https://github.com/kreuzwerker/terraform-provider-docker/issues/180))
-* search local images with Docker image ID ([#151](https://github.com/kreuzwerker/terraform-provider-docker/issues/151))
-* set "ForceNew: true" to labelSchema ([#152](https://github.com/kreuzwerker/terraform-provider-docker/issues/152))
-
-
-
-## [v2.11.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.10.0...v2.11.0) (2021-01-22)
-
-### Chore
-
-* update changelog for v2.11.0
-* updates changelog for v2.10.0
-
-### Docs
-
-* fix legacy configuration style ([#126](https://github.com/kreuzwerker/terraform-provider-docker/issues/126))
-
-### Feat
-
-* add properties -it (tty and stdin_opn) to docker container
-
-
-
-## [v2.10.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.9.0...v2.10.0) (2021-01-08)
-
-### Chore
-
-* updates changelog for 2.10.0
-* ignores testing folders
-* adds separate bug and ft req templates
-
-### Ci
-
-* bumps to docker version 20.10.1
-* pins workflows to ubuntu:20.04 image
-
-### Docs
-
-* add labels to arguments of docker_service ([#105](https://github.com/kreuzwerker/terraform-provider-docker/issues/105))
-* cleans readme
-* adds coc and contributing
-
-### Feat
-
-* supports Docker plugin ([#35](https://github.com/kreuzwerker/terraform-provider-docker/issues/35))
-* support max replicas of Docker Service Task Spec ([#112](https://github.com/kreuzwerker/terraform-provider-docker/issues/112))
-* add force_remove option to r/image ([#104](https://github.com/kreuzwerker/terraform-provider-docker/issues/104))
-* add local semantic commit validation ([#99](https://github.com/kreuzwerker/terraform-provider-docker/issues/99))
-* add ability to lint/check of links in documentation locally ([#98](https://github.com/kreuzwerker/terraform-provider-docker/issues/98))
-
-### Fix
-
-* set "latest" to tag when tag isn't specified ([#117](https://github.com/kreuzwerker/terraform-provider-docker/issues/117))
-* image label for workflows
-* remove all azure cps
-
-### Pull Requests
-
-* Merge pull request [#38](https://github.com/kreuzwerker/terraform-provider-docker/issues/38) from kreuzwerker/ci-ubuntu2004-workflow
-* Merge pull request [#36](https://github.com/kreuzwerker/terraform-provider-docker/issues/36) from kreuzwerker/chore-gh-issue-tpl
-
-
-
-## [v2.9.0](https://github.com/kreuzwerker/terraform-provider-docker/compare/v2.8.0...v2.9.0) (2020-12-25)
-
-### Chore
-
-* updates changelog for 2.9.0
-* update changelog 2.8.0 release date
-* introduces golangci-lint ([#32](https://github.com/kreuzwerker/terraform-provider-docker/issues/32))
-* fix changelog links
-
-### Ci
-
-* add gofmt's '-s' option
-* remove unneeded make tasks
-* fix test of website
-
-### Doc
-
-* devices is a block, not a boolean
-
-### Feat
-
-* adds support for OCI manifests ([#316](https://github.com/kreuzwerker/terraform-provider-docker/issues/316))
-* adds security_opts to container config. ([#308](https://github.com/kreuzwerker/terraform-provider-docker/issues/308))
-* adds support for init process injection for containers. ([#300](https://github.com/kreuzwerker/terraform-provider-docker/issues/300))
-
-### Fix
-
-* changing mounts requires ForceNew ([#314](https://github.com/kreuzwerker/terraform-provider-docker/issues/314))
-* allow healthcheck to be computed as container can specify ([#312](https://github.com/kreuzwerker/terraform-provider-docker/issues/312))
-* treat null user as a no-op ([#318](https://github.com/kreuzwerker/terraform-provider-docker/issues/318))
-* workdir null behavior ([#320](https://github.com/kreuzwerker/terraform-provider-docker/issues/320))
-
-### Style
-
-* format with gofumpt
-
-### Pull Requests
-
-* Merge pull request [#33](https://github.com/kreuzwerker/terraform-provider-docker/issues/33) from brandonros/patch-1
-* Merge pull request [#11](https://github.com/kreuzwerker/terraform-provider-docker/issues/11) from suzuki-shunsuke/format-with-gofumpt
-* Merge pull request [#26](https://github.com/kreuzwerker/terraform-provider-docker/issues/26) from kreuzwerker/ci/fix-website-ci
-* Merge pull request [#8](https://github.com/kreuzwerker/terraform-provider-docker/issues/8) from dubo-dubon-duponey/patch1
-
-
-
-## v2.8.0 (2020-11-11)
-
-### Chore
-
-* updates changelog for 2.8.0
-* removes travis.yml
-* deactivates travis
-* removes vendor dir ([#298](https://github.com/kreuzwerker/terraform-provider-docker/issues/298))
-* bump go 115 ([#297](https://github.com/kreuzwerker/terraform-provider-docker/issues/297))
-* documentation updates ([#286](https://github.com/kreuzwerker/terraform-provider-docker/issues/286))
-* updates link syntax ([#287](https://github.com/kreuzwerker/terraform-provider-docker/issues/287))
-* fix typo ([#292](https://github.com/kreuzwerker/terraform-provider-docker/issues/292))
-
-### Ci
-
-* reactivats all workflows
-* fix website
-* only run website workflow
-* exports gopath manually
-* fix absolute gopath for website
-* make website check separate workflow
-* fix workflow names
-* adds website test to unit test
-* adds acc test
-* adds compile
-* adds go version and goproxy env
-* enables unit tests for master branch
-* adds unit test workflow
-* adds goreleaser and gh action
-* bumps docker and ubuntu versions ([#241](https://github.com/kreuzwerker/terraform-provider-docker/issues/241))
-* removes debug option from acc tests
-* skips test which is flaky only on travis
-
-### Deps
-
-* github.com/hashicorp/terraform[@sdk](https://github.com/sdk)-v0.11-with-go-modules Updated via: go get github.com/hashicorp/terraform[@sdk](https://github.com/sdk)-v0.11-with-go-modules and go mod tidy
-* use go modules for dep mgmt run go mod tidy remove govendor from makefile and travis config set appropriate env vars for go modules
-
-### Docker
-
-* improve validation of runtime constraints
-
-### Docs
-
-* update container.html.markdown ([#278](https://github.com/kreuzwerker/terraform-provider-docker/issues/278))
-* update service.html.markdown ([#281](https://github.com/kreuzwerker/terraform-provider-docker/issues/281))
-* update restart_policy for service. Closes [#228](https://github.com/kreuzwerker/terraform-provider-docker/issues/228)
-* adds new label structure. Closes [#214](https://github.com/kreuzwerker/terraform-provider-docker/issues/214)
-* update anchors with -1 suffix ([#178](https://github.com/kreuzwerker/terraform-provider-docker/issues/178))
-* Fix misspelled words
-* Fix exported attribute name in docker_registry_image
-* Fix example for docker_registry_image ([#8308](https://github.com/kreuzwerker/terraform-provider-docker/issues/8308))
-* provider/docker - network settings attrs
-
-### Feat
-
-* conditionally adding port binding ([#293](https://github.com/kreuzwerker/terraform-provider-docker/issues/293)).
-* adds docker Image build feature ([#283](https://github.com/kreuzwerker/terraform-provider-docker/issues/283))
-* adds complete support for Docker credential helpers ([#253](https://github.com/kreuzwerker/terraform-provider-docker/issues/253))
-* Expose IPv6 properties as attributes
-* allow use of source file instead of content / content_base64 ([#240](https://github.com/kreuzwerker/terraform-provider-docker/issues/240))
-* supports to update docker_container ([#236](https://github.com/kreuzwerker/terraform-provider-docker/issues/236))
-* support to import some docker_container's attributes ([#234](https://github.com/kreuzwerker/terraform-provider-docker/issues/234))
-* adds config file content as plain string ([#232](https://github.com/kreuzwerker/terraform-provider-docker/issues/232))
-* make UID, GID, & mode for secrets and configs configurable ([#231](https://github.com/kreuzwerker/terraform-provider-docker/issues/231))
-* adds import for resources ([#196](https://github.com/kreuzwerker/terraform-provider-docker/issues/196))
-* add container ipc mode. ([#182](https://github.com/kreuzwerker/terraform-provider-docker/issues/182))
-* adds container working dir ([#181](https://github.com/kreuzwerker/terraform-provider-docker/issues/181))
-
-### Fix
-
-* ignores 'remove_volumes' on container import
-* duplicated buildImage function
-* port objects with the same internal port but different protocol trigger recreation of container ([#274](https://github.com/kreuzwerker/terraform-provider-docker/issues/274))
-* panic to migrate schema of docker_container from v1 to v2 ([#271](https://github.com/kreuzwerker/terraform-provider-docker/issues/271)). Closes [#264](https://github.com/kreuzwerker/terraform-provider-docker/issues/264)
-* pins docker registry for tests to v2.7.0
-* prevent force recreate of container about some attributes ([#269](https://github.com/kreuzwerker/terraform-provider-docker/issues/269))
-* service endpoint spec flattening
-* corrects IPAM config read on the data provider ([#229](https://github.com/kreuzwerker/terraform-provider-docker/issues/229))
-* replica to 0 in current schema. Closes [#221](https://github.com/kreuzwerker/terraform-provider-docker/issues/221)
-* label for network and volume after improt
-* binary upload as base 64 content ([#194](https://github.com/kreuzwerker/terraform-provider-docker/issues/194))
-* service env truncation for multiple delimiters ([#193](https://github.com/kreuzwerker/terraform-provider-docker/issues/193))
-* destroy_grace_seconds are considered ([#179](https://github.com/kreuzwerker/terraform-provider-docker/issues/179))
-
-### Make
-
-* Add website + website-test targets
-
-### Provider
-
-* Ensured Go 1.11 in TravisCI and README provider: Run go fix provider: Run go fmt provider: Encode go version 1.11.5 to .go-version file
-* Require Go 1.11 in TravisCI and README provider: Run go fix provider: Run go fmt
-
-### Tests
-
-* Skip test if swap limit isn't available ([#136](https://github.com/kreuzwerker/terraform-provider-docker/issues/136))
-* Simplify Dockerfile(s)
-
-### Vendor
-
-* github.com/hashicorp/terraform/...[@v0](https://github.com/v0).10.0
-* Ignore github.com/hashicorp/terraform/backend
-
-### Website
-
-* Docs sweep for lists & maps
-* note on docker
-* docker docs
-
-### Pull Requests
-
-* Merge pull request [#134](https://github.com/kreuzwerker/terraform-provider-docker/issues/134) from terraform-providers/go-modules-2019-03-01
-* Merge pull request [#135](https://github.com/kreuzwerker/terraform-provider-docker/issues/135) from terraform-providers/t-simplify-dockerfile
-* Merge pull request [#47](https://github.com/kreuzwerker/terraform-provider-docker/issues/47) from captn3m0/docker-link-warning
-* Merge pull request [#60](https://github.com/kreuzwerker/terraform-provider-docker/issues/60) from terraform-providers/f-make-website
-* Merge pull request [#23](https://github.com/kreuzwerker/terraform-provider-docker/issues/23) from JamesLaverack/patch-1
-* Merge pull request [#18](https://github.com/kreuzwerker/terraform-provider-docker/issues/18) from terraform-providers/vendor-tf-0.10
-* Merge pull request [#5046](https://github.com/kreuzwerker/terraform-provider-docker/issues/5046) from tpounds/use-built-in-schema-string-hash
-* Merge pull request [#3761](https://github.com/kreuzwerker/terraform-provider-docker/issues/3761) from ryane/f-provider-docker-improvements
-* Merge pull request [#3383](https://github.com/kreuzwerker/terraform-provider-docker/issues/3383) from apparentlymart/docker-container-command-docs
-* Merge pull request [#1564](https://github.com/kreuzwerker/terraform-provider-docker/issues/1564) from nickryand/docker_links
-
diff --git a/.terraform/providers/registry.terraform.io/kreuzwerker/docker/3.9.0/windows_386/LICENSE b/.terraform/providers/registry.terraform.io/kreuzwerker/docker/3.9.0/windows_386/LICENSE
deleted file mode 100644
index a612ad9..0000000
--- a/.terraform/providers/registry.terraform.io/kreuzwerker/docker/3.9.0/windows_386/LICENSE
+++ /dev/null
@@ -1,373 +0,0 @@
-Mozilla Public License Version 2.0
-==================================
-
-1. Definitions
---------------
-
-1.1. "Contributor"
- means each individual or legal entity that creates, contributes to
- the creation of, or owns Covered Software.
-
-1.2. "Contributor Version"
- means the combination of the Contributions of others (if any) used
- by a Contributor and that particular Contributor's Contribution.
-
-1.3. "Contribution"
- means Covered Software of a particular Contributor.
-
-1.4. "Covered Software"
- means Source Code Form to which the initial Contributor has attached
- the notice in Exhibit A, the Executable Form of such Source Code
- Form, and Modifications of such Source Code Form, in each case
- including portions thereof.
-
-1.5. "Incompatible With Secondary Licenses"
- means
-
- (a) that the initial Contributor has attached the notice described
- in Exhibit B to the Covered Software; or
-
- (b) that the Covered Software was made available under the terms of
- version 1.1 or earlier of the License, but not also under the
- terms of a Secondary License.
-
-1.6. "Executable Form"
- means any form of the work other than Source Code Form.
-
-1.7. "Larger Work"
- means a work that combines Covered Software with other material, in
- a separate file or files, that is not Covered Software.
-
-1.8. "License"
- means this document.
-
-1.9. "Licensable"
- means having the right to grant, to the maximum extent possible,
- whether at the time of the initial grant or subsequently, any and
- all of the rights conveyed by this License.
-
-1.10. "Modifications"
- means any of the following:
-
- (a) any file in Source Code Form that results from an addition to,
- deletion from, or modification of the contents of Covered
- Software; or
-
- (b) any new file in Source Code Form that contains any Covered
- Software.
-
-1.11. "Patent Claims" of a Contributor
- means any patent claim(s), including without limitation, method,
- process, and apparatus claims, in any patent Licensable by such
- Contributor that would be infringed, but for the grant of the
- License, by the making, using, selling, offering for sale, having
- made, import, or transfer of either its Contributions or its
- Contributor Version.
-
-1.12. "Secondary License"
- means either the GNU General Public License, Version 2.0, the GNU
- Lesser General Public License, Version 2.1, the GNU Affero General
- Public License, Version 3.0, or any later versions of those
- licenses.
-
-1.13. "Source Code Form"
- means the form of the work preferred for making modifications.
-
-1.14. "You" (or "Your")
- means an individual or a legal entity exercising rights under this
- License. For legal entities, "You" includes any entity that
- controls, is controlled by, or is under common control with You. For
- purposes of this definition, "control" means (a) the power, direct
- or indirect, to cause the direction or management of such entity,
- whether by contract or otherwise, or (b) ownership of more than
- fifty percent (50%) of the outstanding shares or beneficial
- ownership of such entity.
-
-2. License Grants and Conditions
---------------------------------
-
-2.1. Grants
-
-Each Contributor hereby grants You a world-wide, royalty-free,
-non-exclusive license:
-
-(a) under intellectual property rights (other than patent or trademark)
- Licensable by such Contributor to use, reproduce, make available,
- modify, display, perform, distribute, and otherwise exploit its
- Contributions, either on an unmodified basis, with Modifications, or
- as part of a Larger Work; and
-
-(b) under Patent Claims of such Contributor to make, use, sell, offer
- for sale, have made, import, and otherwise transfer either its
- Contributions or its Contributor Version.
-
-2.2. Effective Date
-
-The licenses granted in Section 2.1 with respect to any Contribution
-become effective for each Contribution on the date the Contributor first
-distributes such Contribution.
-
-2.3. Limitations on Grant Scope
-
-The licenses granted in this Section 2 are the only rights granted under
-this License. No additional rights or licenses will be implied from the
-distribution or licensing of Covered Software under this License.
-Notwithstanding Section 2.1(b) above, no patent license is granted by a
-Contributor:
-
-(a) for any code that a Contributor has removed from Covered Software;
- or
-
-(b) for infringements caused by: (i) Your and any other third party's
- modifications of Covered Software, or (ii) the combination of its
- Contributions with other software (except as part of its Contributor
- Version); or
-
-(c) under Patent Claims infringed by Covered Software in the absence of
- its Contributions.
-
-This License does not grant any rights in the trademarks, service marks,
-or logos of any Contributor (except as may be necessary to comply with
-the notice requirements in Section 3.4).
-
-2.4. Subsequent Licenses
-
-No Contributor makes additional grants as a result of Your choice to
-distribute the Covered Software under a subsequent version of this
-License (see Section 10.2) or under the terms of a Secondary License (if
-permitted under the terms of Section 3.3).
-
-2.5. Representation
-
-Each Contributor represents that the Contributor believes its
-Contributions are its original creation(s) or it has sufficient rights
-to grant the rights to its Contributions conveyed by this License.
-
-2.6. Fair Use
-
-This License is not intended to limit any rights You have under
-applicable copyright doctrines of fair use, fair dealing, or other
-equivalents.
-
-2.7. Conditions
-
-Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
-in Section 2.1.
-
-3. Responsibilities
--------------------
-
-3.1. Distribution of Source Form
-
-All distribution of Covered Software in Source Code Form, including any
-Modifications that You create or to which You contribute, must be under
-the terms of this License. You must inform recipients that the Source
-Code Form of the Covered Software is governed by the terms of this
-License, and how they can obtain a copy of this License. You may not
-attempt to alter or restrict the recipients' rights in the Source Code
-Form.
-
-3.2. Distribution of Executable Form
-
-If You distribute Covered Software in Executable Form then:
-
-(a) such Covered Software must also be made available in Source Code
- Form, as described in Section 3.1, and You must inform recipients of
- the Executable Form how they can obtain a copy of such Source Code
- Form by reasonable means in a timely manner, at a charge no more
- than the cost of distribution to the recipient; and
-
-(b) You may distribute such Executable Form under the terms of this
- License, or sublicense it under different terms, provided that the
- license for the Executable Form does not attempt to limit or alter
- the recipients' rights in the Source Code Form under this License.
-
-3.3. Distribution of a Larger Work
-
-You may create and distribute a Larger Work under terms of Your choice,
-provided that You also comply with the requirements of this License for
-the Covered Software. If the Larger Work is a combination of Covered
-Software with a work governed by one or more Secondary Licenses, and the
-Covered Software is not Incompatible With Secondary Licenses, this
-License permits You to additionally distribute such Covered Software
-under the terms of such Secondary License(s), so that the recipient of
-the Larger Work may, at their option, further distribute the Covered
-Software under the terms of either this License or such Secondary
-License(s).
-
-3.4. Notices
-
-You may not remove or alter the substance of any license notices
-(including copyright notices, patent notices, disclaimers of warranty,
-or limitations of liability) contained within the Source Code Form of
-the Covered Software, except that You may alter any license notices to
-the extent required to remedy known factual inaccuracies.
-
-3.5. Application of Additional Terms
-
-You may choose to offer, and to charge a fee for, warranty, support,
-indemnity or liability obligations to one or more recipients of Covered
-Software. However, You may do so only on Your own behalf, and not on
-behalf of any Contributor. You must make it absolutely clear that any
-such warranty, support, indemnity, or liability obligation is offered by
-You alone, and You hereby agree to indemnify every Contributor for any
-liability incurred by such Contributor as a result of warranty, support,
-indemnity or liability terms You offer. You may include additional
-disclaimers of warranty and limitations of liability specific to any
-jurisdiction.
-
-4. Inability to Comply Due to Statute or Regulation
----------------------------------------------------
-
-If it is impossible for You to comply with any of the terms of this
-License with respect to some or all of the Covered Software due to
-statute, judicial order, or regulation then You must: (a) comply with
-the terms of this License to the maximum extent possible; and (b)
-describe the limitations and the code they affect. Such description must
-be placed in a text file included with all distributions of the Covered
-Software under this License. Except to the extent prohibited by statute
-or regulation, such description must be sufficiently detailed for a
-recipient of ordinary skill to be able to understand it.
-
-5. Termination
---------------
-
-5.1. The rights granted under this License will terminate automatically
-if You fail to comply with any of its terms. However, if You become
-compliant, then the rights granted under this License from a particular
-Contributor are reinstated (a) provisionally, unless and until such
-Contributor explicitly and finally terminates Your grants, and (b) on an
-ongoing basis, if such Contributor fails to notify You of the
-non-compliance by some reasonable means prior to 60 days after You have
-come back into compliance. Moreover, Your grants from a particular
-Contributor are reinstated on an ongoing basis if such Contributor
-notifies You of the non-compliance by some reasonable means, this is the
-first time You have received notice of non-compliance with this License
-from such Contributor, and You become compliant prior to 30 days after
-Your receipt of the notice.
-
-5.2. If You initiate litigation against any entity by asserting a patent
-infringement claim (excluding declaratory judgment actions,
-counter-claims, and cross-claims) alleging that a Contributor Version
-directly or indirectly infringes any patent, then the rights granted to
-You by any and all Contributors for the Covered Software under Section
-2.1 of this License shall terminate.
-
-5.3. In the event of termination under Sections 5.1 or 5.2 above, all
-end user license agreements (excluding distributors and resellers) which
-have been validly granted by You or Your distributors under this License
-prior to termination shall survive termination.
-
-************************************************************************
-* *
-* 6. Disclaimer of Warranty *
-* ------------------------- *
-* *
-* Covered Software is provided under this License on an "as is" *
-* basis, without warranty of any kind, either expressed, implied, or *
-* statutory, including, without limitation, warranties that the *
-* Covered Software is free of defects, merchantable, fit for a *
-* particular purpose or non-infringing. The entire risk as to the *
-* quality and performance of the Covered Software is with You. *
-* Should any Covered Software prove defective in any respect, You *
-* (not any Contributor) assume the cost of any necessary servicing, *
-* repair, or correction. This disclaimer of warranty constitutes an *
-* essential part of this License. No use of any Covered Software is *
-* authorized under this License except under this disclaimer. *
-* *
-************************************************************************
-
-************************************************************************
-* *
-* 7. Limitation of Liability *
-* -------------------------- *
-* *
-* Under no circumstances and under no legal theory, whether tort *
-* (including negligence), contract, or otherwise, shall any *
-* Contributor, or anyone who distributes Covered Software as *
-* permitted above, be liable to You for any direct, indirect, *
-* special, incidental, or consequential damages of any character *
-* including, without limitation, damages for lost profits, loss of *
-* goodwill, work stoppage, computer failure or malfunction, or any *
-* and all other commercial damages or losses, even if such party *
-* shall have been informed of the possibility of such damages. This *
-* limitation of liability shall not apply to liability for death or *
-* personal injury resulting from such party's negligence to the *
-* extent applicable law prohibits such limitation. Some *
-* jurisdictions do not allow the exclusion or limitation of *
-* incidental or consequential damages, so this exclusion and *
-* limitation may not apply to You. *
-* *
-************************************************************************
-
-8. Litigation
--------------
-
-Any litigation relating to this License may be brought only in the
-courts of a jurisdiction where the defendant maintains its principal
-place of business and such litigation shall be governed by laws of that
-jurisdiction, without reference to its conflict-of-law provisions.
-Nothing in this Section shall prevent a party's ability to bring
-cross-claims or counter-claims.
-
-9. Miscellaneous
-----------------
-
-This License represents the complete agreement concerning the subject
-matter hereof. If any provision of this License is held to be
-unenforceable, such provision shall be reformed only to the extent
-necessary to make it enforceable. Any law or regulation which provides
-that the language of a contract shall be construed against the drafter
-shall not be used to construe this License against a Contributor.
-
-10. Versions of the License
----------------------------
-
-10.1. New Versions
-
-Mozilla Foundation is the license steward. Except as provided in Section
-10.3, no one other than the license steward has the right to modify or
-publish new versions of this License. Each version will be given a
-distinguishing version number.
-
-10.2. Effect of New Versions
-
-You may distribute the Covered Software under the terms of the version
-of the License under which You originally received the Covered Software,
-or under the terms of any subsequent version published by the license
-steward.
-
-10.3. Modified Versions
-
-If you create software not governed by this License, and you want to
-create a new license for such software, you may create and use a
-modified version of this License if you rename the license and remove
-any references to the name of the license steward (except to note that
-such modified license differs from this License).
-
-10.4. Distributing Source Code Form that is Incompatible With Secondary
-Licenses
-
-If You choose to distribute Source Code Form that is Incompatible With
-Secondary Licenses under the terms of this version of the License, the
-notice described in Exhibit B of this License must be attached.
-
-Exhibit A - Source Code Form License Notice
--------------------------------------------
-
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-If it is not possible or desirable to put the notice in a particular
-file, then You may include the notice in a location (such as a LICENSE
-file in a relevant directory) where a recipient would be likely to look
-for such a notice.
-
-You may add additional accurate notices of copyright ownership.
-
-Exhibit B - "Incompatible With Secondary Licenses" Notice
----------------------------------------------------------
-
- This Source Code Form is "Incompatible With Secondary Licenses", as
- defined by the Mozilla Public License, v. 2.0.
diff --git a/.terraform/providers/registry.terraform.io/kreuzwerker/docker/3.9.0/windows_386/README.md b/.terraform/providers/registry.terraform.io/kreuzwerker/docker/3.9.0/windows_386/README.md
deleted file mode 100644
index 6be3b5f..0000000
--- a/.terraform/providers/registry.terraform.io/kreuzwerker/docker/3.9.0/windows_386/README.md
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-# Terraform Provider for Docker
-
-[](https://github.com/kreuzwerker/terraform-provider-docker/releases)
-[](https://registry.terraform.io/providers/kreuzwerker/docker)
-[](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs)
-[](https://github.com/kreuzwerker/terraform-provider-docker/blob/main/LICENSE)
-[](https://github.com/kreuzwerker/terraform-provider-docker/actions)
-[](https://github.com/kreuzwerker/terraform-provider-docker/actions)
-[](https://goreportcard.com/report/github.com/kreuzwerker/terraform-provider-docker)
-
-## Documentation
-
-The documentation for the provider is available on the [Terraform Registry](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs).
-
-Do you want to migrate from `v2.x` to `v3.x`? Please read the [migration guide](docs/v2_v3_migration.md)
-
-## Example usage
-
-Take a look at the examples in the [documentation](https://registry.terraform.io/providers/kreuzwerker/docker/3.9.0/docs) of the registry
-or use the following example:
-
-
-```hcl
-# Set the required provider and versions
-terraform {
- required_providers {
- # We recommend pinning to the specific version of the Docker Provider you're using
- # since new versions are released frequently
- docker = {
- source = "kreuzwerker/docker"
- version = "3.9.0"
- }
- }
-}
-
-# Configure the docker provider
-provider "docker" {
-}
-
-# Create a docker image resource
-# -> docker pull nginx:latest
-resource "docker_image" "nginx" {
- name = "nginx:latest"
- keep_locally = true
-}
-
-# Create a docker container resource
-# -> same as 'docker run --name nginx -p8080:80 -d nginx:latest'
-resource "docker_container" "nginx" {
- name = "nginx"
- image = docker_image.nginx.image_id
-
- ports {
- external = 8080
- internal = 80
- }
-}
-
-# Or create a service resource
-# -> same as 'docker service create -d -p 8081:80 --name nginx-service --replicas 2 nginx:latest'
-resource "docker_service" "nginx_service" {
- name = "nginx-service"
- task_spec {
- container_spec {
- image = docker_image.nginx.repo_digest
- }
- }
-
- mode {
- replicated {
- replicas = 2
- }
- }
-
- endpoint_spec {
- ports {
- published_port = 8081
- target_port = 80
- }
- }
-}
-```
-
-## Building The Provider
-
-[Go](https://golang.org/doc/install) 1.18.x (to build the provider plugin)
-
-
-```sh
-$ git clone git@github.com:kreuzwerker/terraform-provider-docker
-$ make build
-```
-
-## Contributing
-
-The Terraform Docker Provider is the work of many of contributors. We appreciate your help!
-
-To contribute, please read the contribution guidelines: [Contributing to Terraform - Docker Provider](CONTRIBUTING.md)
-
-## License
-
-The Terraform Provider Docker is available to everyone under the terms of the Mozilla Public License Version 2.0. [Take a look the LICENSE file](LICENSE).
-
-
-## Stargazers over time
-
-[](https://starchart.cc/kreuzwerker/terraform-provider-docker)
diff --git a/Makefile b/Makefile
index 71dea97..81c3e6d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,8 @@
BACKEND := apps/backend
.DEFAULT_GOAL := help
-.PHONY: help install dev lint format typecheck test check docker-build
+.PHONY: help install dev lint format typecheck test test-cov test-integration check \
+ docker-build db-up db-down db-reset db-logs db-psql migrate
help: ## Liste les cibles disponibles
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}'
@@ -21,10 +22,35 @@ format: ## Formate et corrige le backend
typecheck: ## Verifie le typage du backend
cd $(BACKEND) && uv run mypy app
-test: ## Execute les tests backend
- cd $(BACKEND) && uv run pytest
+test: ## Execute les tests backend ne demandant pas de base
+ cd $(BACKEND) && uv run pytest --cov-fail-under=85
+
+test-cov: ## Rapports de couverture HTML et XML, plus les resultats au format JUnit
+ cd $(BACKEND) && uv run pytest --cov-fail-under=85 --cov-report=html \
+ --cov-report=xml --junitxml=test-results/junit.xml
+
+test-integration: ## Execute les tests exigeant une base joignable
+ cd $(BACKEND) && uv run pytest -m integration
check: lint typecheck test ## Chaine de verification complete
docker-build: ## Construit l'image du backend
docker build -t enervision-backend:local $(BACKEND)
+
+db-up: ## Demarre la base PostgreSQL TimescaleDB
+ docker compose up -d db
+
+db-down: ## Arrete la base en conservant ses donnees
+ docker compose stop db
+
+db-reset: ## Detruit la base et rejoue db/init
+ docker compose down -v && docker compose up -d db
+
+db-logs: ## Suit les journaux de la base
+ docker compose logs -f db
+
+db-psql: ## Ouvre une session psql sur la base applicative
+ docker compose exec db psql -U $${POSTGRES_USER:-enervision} -d $${POSTGRES_DB:-enervision}
+
+migrate: ## Applique les migrations Alembic
+ cd $(BACKEND) && uv run alembic upgrade head
diff --git a/README.md b/README.md
index b6ab9c8..3302c64 100644
--- a/README.md
+++ b/README.md
@@ -9,14 +9,14 @@ series temporelles energetiques, deployee sur une machine on-premise.
|------------|-------------------------------------|---------------------|---------------|
| Backend | FastAPI, Python 3.14 | `apps/backend` | Initialise |
| Frontend | Angular, Node 24 LTS | `apps/frontend` | A initialiser |
-| Base | PostgreSQL + TimescaleDB | `db` | A initialiser |
+| Base | PostgreSQL 17 + TimescaleDB | `db` | Initialise |
| ETL | Apache Airflow | `etl/airflow` | A initialiser |
-| Infra | Terraform | `infra/terraform` | A initialiser |
+| Infra | Terraform (k3s single-node) | `infra/terraform` | Initialise |
| CI/CD | GitHub Actions | `.github/workflows` | A initialiser |
| Monitoring | Prometheus, Grafana, Alertmanager | `monitoring` | A initialiser |
-Seul le backend est initialise a ce stade. Les autres dossiers portent l'arborescence et
-un README de cadrage, leur contenu fait l'objet d'un ticket dedie.
+Le backend, la base et l'infrastructure (Terraform/k3s) sont initialises a ce stade. Les autres dossiers
+portent l'arborescence et un README de cadrage, leur contenu fait l'objet d'un ticket dedie.
## Arborescence
@@ -50,15 +50,35 @@ un README de cadrage, leur contenu fait l'objet d'un ticket dedie.
Prerequis : uv, Docker. Le poste doit disposer de Python 3.14, que `uv` installe seul.
```bash
+cp .env.example .env # variables de docker-compose
+cp apps/backend/.env.example apps/backend/.env # variables du backend hors conteneur
+
+make db-up # PostgreSQL + TimescaleDB, publie sur le port 5433
make install # dependances du backend
+make migrate # applique les migrations Alembic
make dev # API sur http://localhost:8000, docs sur /docs
make check # lint + typage + tests
```
`make help` liste les cibles disponibles.
+Deux fichiers d'environnement, deux usages : `.env` a la racine alimente `docker-compose.yml`,
+`apps/backend/.env` alimente le backend lance sur le poste. Le port 5433 est publie plutot que
+5432, souvent deja pris par une autre base.
+
+La boucle de developpement est `make db-up` puis `make dev` : seule la base tourne en
+conteneur. Le service `backend` du `docker-compose.yml` sert la stack complete et la recette,
+et n'embarque pas le source, donc toute modification y demande un
+`docker compose up -d --build backend`.
+
+Verifier que la base repond et que l'extension est chargee :
+
+```bash
+curl -s localhost:8000/api/v1/health/ready
+```
+
## Conventions
-- Branches : `feat/`, `fix/`, `chore/`, `docs/` suivi d'un libelle court.
+- Branches : `feat/`, `fix/`, `chore/`, `docs/`, `test/` suivi d'un libelle court.
- Commits : Conventional Commits, portee = dossier de premier niveau concerne.
- Toute decision structurante donne lieu a un ADR dans `docs/adr`.
diff --git a/apps/backend/.env.example b/apps/backend/.env.example
index 258db03..cd96463 100644
--- a/apps/backend/.env.example
+++ b/apps/backend/.env.example
@@ -3,4 +3,4 @@ APP_DEBUG=true
APP_LOG_LEVEL=INFO
APP_SECRET_KEY=change_me
APP_CORS_ORIGINS=http://localhost:4200
-DATABASE_URL=postgresql+asyncpg://enervision:change_me@localhost:5432/enervision
+DATABASE_URL=postgresql+asyncpg://enervision:change_me@localhost:5433/enervision
diff --git a/apps/backend/README.md b/apps/backend/README.md
index bd1c6fa..5d940b8 100644
--- a/apps/backend/README.md
+++ b/apps/backend/README.md
@@ -22,6 +22,9 @@ uv sync --all-groups
`APP_SECRET_KEY` et `DATABASE_URL` n'ont pas de valeur par defaut : l'application refuse
de demarrer sans elles.
+`DATABASE_URL` pointe sur `localhost:5433`, le port publie par le service `db` du
+`docker-compose.yml` racine. Demarrer la base depuis la racine avec `make db-up`.
+
## Commandes
Depuis la racine du monorepo, via le `Makefile` : `make install`, `make dev`, `make lint`,
@@ -35,8 +38,16 @@ uv run ruff check . # lint
uv run ruff format . # format
uv run mypy app # typage strict
uv run pytest # tests + couverture
+uv run pytest -m integration # tests exigeant une base joignable
```
+Les conventions de tests, les gabarits et le detail des marqueurs sont dans
+[`TESTING.md`](TESTING.md).
+
+`pytest` ecarte par defaut les tests marques `integration`, pour que `make check` reste
+jouable sans Docker. Ces tests visent la base `enervision_test`, creee par
+`db/init/110-test-database.sql` au premier demarrage du conteneur.
+
L'application est exposee par une factory (`create_app`) et non par un objet module :
aucune configuration n'est lue a l'import, ce qui rend les tests et les migrations
independants de l'environnement.
@@ -73,7 +84,7 @@ Le sens de dependance est unique : `endpoints` vers `services` vers `repositorie
| Route | Role |
|------------------------|-------------------------------------------------|
| `/api/v1/health/live` | Sonde de vivacite, aucune dependance externe |
-| `/api/v1/health/ready` | Sonde de disponibilite, verifie la base |
+| `/api/v1/health/ready` | Sonde de disponibilite, verifie la base et TimescaleDB |
| `/metrics` | Metriques au format Prometheus |
| `/docs`, `/openapi.json` | Documentation, desactivee quand `APP_ENV=prod` |
@@ -86,6 +97,10 @@ uv run alembic upgrade head
L'URL de connexion vient de `DATABASE_URL`, pas de `alembic.ini`.
+La premiere revision ne cree aucune table : elle refuse de s'appliquer si l'extension
+TimescaleDB manque, ce qui arrive quand `db/init` n'a pas ete joue. Le DDL propre a
+TimescaleDB qui ne depend pas du schema applicatif vit dans `db/`, pas ici.
+
## Image Docker
Build multi-stage, dependances resolues par uv depuis `uv.lock`, execution sous un
diff --git a/apps/backend/TESTING.md b/apps/backend/TESTING.md
new file mode 100644
index 0000000..f794421
--- /dev/null
+++ b/apps/backend/TESTING.md
@@ -0,0 +1,143 @@
+# Conventions de tests unitaires : Backend
+
+## Outil
+
+pytest, avec pytest-asyncio en mode `auto` : un `async def test_*` est collecte sans
+decorateur. Les appels HTTP passent par httpx sur `ASGITransport`, qui parle a
+l'application en memoire, sans serveur ni port ouvert.
+
+## Ou ecrire les tests
+
+`tests/` est le miroir de `app/` : un test de `app/services/consumption.py` va dans
+`tests/services/test_consumption.py`. Les paquets `core`, `db`, `services` et
+`repositories` existent deja, vides, pour cette raison.
+
+## Nommage
+
+- Fonctions en anglais : `test___when_`.
+- `ids=` de `parametrize` en francais : `ids=["erreur_sqlalchemy", "erreur_reseau"]`.
+- Pas de docstring : le nom porte l'intention.
+
+## Structure attendue (Arrange / Act / Assert)
+
+Une ligne vide separe les trois temps, sans commentaire pour les annoncer.
+
+```python
+async def test_readiness_returns_503_when_the_extension_is_missing(
+ fake_session: Callable[..., None], client: AsyncClient
+) -> None:
+ fake_session(result=None)
+
+ response = await client.get("/api/v1/health/ready")
+
+ assert response.status_code == 503
+ assert response.json()["detail"] == "Extension TimescaleDB absente"
+```
+
+## Ce qui doit etre teste en priorite
+
+Le sens de dependance du backend est `endpoints -> services -> repositories -> models`.
+
+| Couche | Ce qu'on teste |
+|---|---|
+| `services/` | La logique metier, cas nominal et cas d'erreur. C'est la priorite. |
+| `repositories/` | Chaque branche de decision, sous le marqueur `integration`. |
+| `endpoints/` | Le code de statut et la forme de la reponse, pas la logique metier. |
+| `schemas/` | Rien, sauf si le schema porte une validation ecrite a la main. |
+
+## Doubles
+
+On remplace une dependance FastAPI par `app.dependency_overrides`, jamais par
+`unittest.mock`. `tests/factories.py` fournit le necessaire.
+
+- `fake_session(result=...)` : la session repond `result`.
+- `fake_session(failure=...)` : la session leve l'exception.
+- `make_settings(**overrides)` : fabrique une `Settings`, dont les valeurs priment sur
+ l'environnement et sur `.env`. C'est le moyen de tester `create_app` en `prod`.
+
+## Gabarit : un endpoint
+
+```python
+from collections.abc import Callable
+
+from httpx import AsyncClient
+
+
+async def test_endpoint_returns_the_expected_payload(
+ fake_session: Callable[..., None], client: AsyncClient
+) -> None:
+ fake_session(result=42)
+
+ response = await client.get("/api/v1/...")
+
+ assert response.status_code == 200
+ assert response.json() == {"valeur": 42}
+```
+
+## Gabarit : un service avec repository factice
+
+Un service ne connait que son repository : on lui en passe un faux, sans base ni session.
+
+```python
+from app.services.consumption import ConsumptionService
+
+
+class FakeRepository:
+ async def total_for(self, site_id: int) -> float:
+ return 12.5
+
+
+async def test_service_converts_the_total_to_kilowatt_hours() -> None:
+ service = ConsumptionService(FakeRepository())
+
+ total = await service.total_kwh(site_id=1)
+
+ assert total == 12.5
+```
+
+## Gabarit : un repository sur la vraie base
+
+Un repository parle du SQL : le tester sur un double ne prouve rien. Il porte donc le
+marqueur `integration`, ecarte par defaut.
+
+```python
+import pytest
+from sqlalchemy.ext.asyncio import AsyncSession
+
+from app.models.site import Site
+from app.repositories.site import SiteRepository
+
+
+@pytest.mark.integration
+async def test_repository_reads_back_what_it_wrote(session: AsyncSession) -> None:
+ repository = SiteRepository(session)
+
+ await repository.add(Site(name="Toulouse"))
+
+ assert await repository.by_name("Toulouse") is not None
+```
+
+## Marqueurs
+
+`integration` designe tout test exigeant une base joignable. `pytest` les ecarte par
+defaut, ce qui garde `make check` jouable sans Docker. Tout autre marqueur doit etre
+declare dans `pyproject.toml` : `--strict-markers` refuse les marqueurs inconnus.
+
+## Couverture
+
+Les branches sont mesurees, pas seulement les lignes. Le seuil de 85 % ne s'applique
+qu'aux cibles qui jouent toute la suite, `make test` et `make test-cov` : un fichier
+joue seul affiche sa couverture sans jamais echouer dessus. Le detail se lit dans
+`htmlcov/index.html` apres `make test-cov`.
+
+## Lancer les tests
+
+```bash
+make test # suite unitaire, sans base
+make test-cov # idem, plus les rapports HTML, XML et JUnit
+make db-up && make test-integration # tests exigeant une base, demande Docker
+make check # lint + typage + suite unitaire
+
+uv run pytest tests/api/test_health.py # un seul fichier
+uv run pytest -k readiness # par motif de nom
+```
diff --git a/apps/backend/alembic/versions/5353c0e4f094_socle_garde_extension_timescaledb.py b/apps/backend/alembic/versions/5353c0e4f094_socle_garde_extension_timescaledb.py
new file mode 100644
index 0000000..de14bd3
--- /dev/null
+++ b/apps/backend/alembic/versions/5353c0e4f094_socle_garde_extension_timescaledb.py
@@ -0,0 +1,37 @@
+"""socle garde extension timescaledb
+
+Revision ID: 5353c0e4f094
+Revises:
+Create Date: 2026-09-14 14:17:17.556764
+
+Premiere revision du schema applicatif. Elle ne cree aucune table : elle etablit
+alembic_version et refuse de s'appliquer sur une base ou l'extension TimescaleDB
+manque, cas qui se produit quand db/init n'a pas ete joue.
+"""
+
+from collections.abc import Sequence
+
+from alembic import op
+
+revision: str = "5353c0e4f094"
+down_revision: str | Sequence[str] | None = None
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+GARDE_EXTENSION = """
+DO $$
+BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'timescaledb') THEN
+ RAISE EXCEPTION 'extension timescaledb absente, voir db/init et db/README.md';
+ END IF;
+END
+$$;
+"""
+
+
+def upgrade() -> None:
+ op.execute(GARDE_EXTENSION)
+
+
+def downgrade() -> None:
+ pass
diff --git a/apps/backend/app/api/v1/endpoints/health.py b/apps/backend/app/api/v1/endpoints/health.py
index b3c8523..be3abf8 100644
--- a/apps/backend/app/api/v1/endpoints/health.py
+++ b/apps/backend/app/api/v1/endpoints/health.py
@@ -9,6 +9,8 @@ from app.schemas.health import LivenessStatus, ReadinessStatus
logger = get_logger(__name__)
router = APIRouter(tags=["health"])
+TIMESCALEDB_VERSION = text("SELECT extversion FROM pg_extension WHERE extname = 'timescaledb'")
+
@router.get("/live", summary="Sonde de vivacite")
async def liveness(settings: SettingsDep) -> LivenessStatus:
@@ -23,11 +25,19 @@ async def liveness(settings: SettingsDep) -> LivenessStatus:
@router.get("/ready", summary="Sonde de disponibilite")
async def readiness(session: SessionDep) -> ReadinessStatus:
try:
- await session.execute(text("SELECT 1"))
+ version: str | None = await session.scalar(TIMESCALEDB_VERSION)
except SQLAlchemyError, OSError:
logger.exception("Base de donnees injoignable")
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Base de donnees injoignable",
) from None
- return ReadinessStatus(status="ready", database="reachable")
+
+ if version is None:
+ logger.error("Extension TimescaleDB absente de la base")
+ raise HTTPException(
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
+ detail="Extension TimescaleDB absente",
+ )
+
+ return ReadinessStatus(status="ready", database="reachable", timescaledb=version)
diff --git a/apps/backend/app/schemas/health.py b/apps/backend/app/schemas/health.py
index d1eb845..e4ec86e 100644
--- a/apps/backend/app/schemas/health.py
+++ b/apps/backend/app/schemas/health.py
@@ -13,3 +13,4 @@ class LivenessStatus(BaseModel):
class ReadinessStatus(BaseModel):
status: Literal["ready"]
database: Literal["reachable"]
+ timescaledb: str
diff --git a/apps/backend/pyproject.toml b/apps/backend/pyproject.toml
index 87c916a..1c27c08 100644
--- a/apps/backend/pyproject.toml
+++ b/apps/backend/pyproject.toml
@@ -79,8 +79,14 @@ disallow_untyped_defs = false
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
-addopts = "-q --strict-markers --cov=app --cov-report=term-missing"
+asyncio_default_fixture_loop_scope = "function"
+addopts = "-q --strict-markers -m 'not integration' --cov=app --cov-report=term-missing"
+markers = ["integration: requiert une base PostgreSQL joignable, hors `make test`"]
[tool.coverage.run]
source = ["app"]
-omit = ["app/main.py", "alembic/*"]
+branch = true
+omit = ["alembic/*"]
+
+[tool.coverage.report]
+show_missing = true
diff --git a/apps/backend/tests/api/test_health.py b/apps/backend/tests/api/test_health.py
index 6a2f938..a7a61b6 100644
--- a/apps/backend/tests/api/test_health.py
+++ b/apps/backend/tests/api/test_health.py
@@ -1,12 +1,9 @@
-from collections.abc import AsyncIterator
+from collections.abc import Callable
import pytest
-from fastapi import FastAPI
from httpx import AsyncClient
from sqlalchemy.exc import OperationalError
-from app.db.session import get_session
-
async def test_liveness_exposes_service_metadata(client: AsyncClient) -> None:
response = await client.get("/api/v1/health/live")
@@ -20,6 +17,32 @@ async def test_liveness_exposes_service_metadata(client: AsyncClient) -> None:
}
+async def test_readiness_reports_the_timescaledb_version(
+ fake_session: Callable[..., None], client: AsyncClient
+) -> None:
+ fake_session(result="2.22.1")
+
+ response = await client.get("/api/v1/health/ready")
+
+ assert response.status_code == 200
+ assert response.json() == {
+ "status": "ready",
+ "database": "reachable",
+ "timescaledb": "2.22.1",
+ }
+
+
+async def test_readiness_returns_503_when_the_extension_is_missing(
+ fake_session: Callable[..., None], client: AsyncClient
+) -> None:
+ fake_session(result=None)
+
+ response = await client.get("/api/v1/health/ready")
+
+ assert response.status_code == 503
+ assert response.json()["detail"] == "Extension TimescaleDB absente"
+
+
@pytest.mark.parametrize(
"failure",
[
@@ -29,16 +52,9 @@ async def test_liveness_exposes_service_metadata(client: AsyncClient) -> None:
ids=["erreur_sqlalchemy", "erreur_reseau_asyncpg"],
)
async def test_readiness_returns_503_when_database_is_unreachable(
- app: FastAPI, client: AsyncClient, failure: Exception
+ fake_session: Callable[..., None], client: AsyncClient, failure: Exception
) -> None:
- class UnreachableSession:
- async def execute(self, *_: object, **__: object) -> None:
- raise failure
-
- async def override() -> AsyncIterator[UnreachableSession]:
- yield UnreachableSession()
-
- app.dependency_overrides[get_session] = override
+ fake_session(failure=failure)
response = await client.get("/api/v1/health/ready")
@@ -49,3 +65,14 @@ async def test_readiness_returns_503_when_database_is_unreachable(
@pytest.mark.parametrize("path", ["/openapi.json", "/metrics"])
async def test_technical_endpoints_are_served(client: AsyncClient, path: str) -> None:
assert (await client.get(path)).status_code == 200
+
+
+@pytest.mark.integration
+async def test_readiness_reaches_the_real_database(client: AsyncClient) -> None:
+ response = await client.get("/api/v1/health/ready")
+
+ assert response.status_code == 200, response.text
+ body = response.json()
+ assert body["status"] == "ready"
+ assert body["database"] == "reachable"
+ assert body["timescaledb"]
diff --git a/apps/backend/tests/conftest.py b/apps/backend/tests/conftest.py
index d0d5873..4560b75 100644
--- a/apps/backend/tests/conftest.py
+++ b/apps/backend/tests/conftest.py
@@ -1,25 +1,49 @@
import os
-from collections.abc import AsyncIterator, Iterator
+from collections.abc import AsyncIterator, Callable, Iterator
import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient
+from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import get_settings
+from app.db.session import get_engine, get_session, get_session_factory
from app.main import create_app
+from tests.factories import FakeSession
+# Piege : les variables d'environnement priment sur apps/backend/.env. Celles qu'on ne
+# pose pas ici, c'est le .env du poste qui les decide, et les assertions avec.
@pytest.fixture(autouse=True, scope="session")
def environment() -> Iterator[None]:
- os.environ.setdefault("APP_SECRET_KEY", "secret-de-test")
+ os.environ.update(
+ {
+ "APP_ENV": "local",
+ "APP_DEBUG": "false",
+ "APP_LOG_LEVEL": "WARNING",
+ "APP_CORS_ORIGINS": "",
+ "APP_SECRET_KEY": "secret-de-test",
+ }
+ )
os.environ.setdefault(
- "DATABASE_URL", "postgresql+asyncpg://enervision:enervision@localhost:5432/enervision_test"
+ "DATABASE_URL", "postgresql+asyncpg://enervision:change_me@localhost:5433/enervision_test"
)
get_settings.cache_clear()
yield
get_settings.cache_clear()
+# Piege : get_engine est lru_cache et pytest-asyncio ouvre une boucle par test. Sans ce
+# recyclage, le 2e test touchant vraiment la base heriterait d une boucle morte.
+@pytest.fixture(autouse=True)
+async def engine_per_test() -> AsyncIterator[None]:
+ yield
+ if get_engine.cache_info().currsize:
+ await get_engine().dispose()
+ get_engine.cache_clear()
+ get_session_factory.cache_clear()
+
+
@pytest.fixture
def app() -> FastAPI:
return create_app()
@@ -30,3 +54,21 @@ async def client(app: FastAPI) -> AsyncIterator[AsyncClient]:
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as async_client:
yield async_client
+
+
+@pytest.fixture
+def fake_session(app: FastAPI) -> Callable[..., None]:
+ def install(result: object = None, failure: Exception | None = None) -> None:
+ async def override() -> AsyncIterator[FakeSession]:
+ yield FakeSession(result=result, failure=failure)
+
+ app.dependency_overrides[get_session] = override
+
+ return install
+
+
+# Contrainte : ouvre une vraie connexion, donc reservee aux tests `integration`.
+@pytest.fixture
+async def session() -> AsyncIterator[AsyncSession]:
+ async with get_session_factory()() as async_session:
+ yield async_session
diff --git a/db/init/.gitkeep b/apps/backend/tests/core/__init__.py
similarity index 100%
rename from db/init/.gitkeep
rename to apps/backend/tests/core/__init__.py
diff --git a/docs/adr/.gitkeep b/apps/backend/tests/db/__init__.py
similarity index 100%
rename from docs/adr/.gitkeep
rename to apps/backend/tests/db/__init__.py
diff --git a/apps/backend/tests/factories.py b/apps/backend/tests/factories.py
new file mode 100644
index 0000000..05ba3fc
--- /dev/null
+++ b/apps/backend/tests/factories.py
@@ -0,0 +1,37 @@
+from typing import Any
+
+from app.core.config import Settings
+
+SETTINGS_DE_TEST: dict[str, Any] = {
+ "env": "local",
+ "debug": False,
+ "log_level": "WARNING",
+ "cors_origins": "",
+ "secret_key": "secret-de-test",
+ "database_url": "postgresql+asyncpg://enervision:change_me@localhost:5433/enervision_test",
+}
+
+
+class FakeSession:
+ """Session factice : renvoie `result`, ou leve `failure` si elle est fournie."""
+
+ def __init__(self, result: object = None, failure: Exception | None = None) -> None:
+ self._result = result
+ self._failure = failure
+
+ async def scalar(self, *_: object, **__: object) -> object:
+ return self._repondre()
+
+ async def execute(self, *_: object, **__: object) -> object:
+ return self._repondre()
+
+ def _repondre(self) -> object:
+ if self._failure is not None:
+ raise self._failure
+ return self._result
+
+
+# Piege : les arguments nommes priment sur l'environnement et sur .env, contrairement
+# aux variables posees par la fixture `environment`, qui restent surchargeables.
+def make_settings(**overrides: Any) -> Settings:
+ return Settings(**{**SETTINGS_DE_TEST, **overrides})
diff --git a/infra/terraform/environments/dev/.gitkeep b/apps/backend/tests/repositories/__init__.py
similarity index 100%
rename from infra/terraform/environments/dev/.gitkeep
rename to apps/backend/tests/repositories/__init__.py
diff --git a/infra/terraform/modules/.gitkeep b/apps/backend/tests/services/__init__.py
similarity index 100%
rename from infra/terraform/modules/.gitkeep
rename to apps/backend/tests/services/__init__.py
diff --git a/apps/frontend/TESTING.md b/apps/frontend/TESTING.md
new file mode 100644
index 0000000..e23ed7a
--- /dev/null
+++ b/apps/frontend/TESTING.md
@@ -0,0 +1,81 @@
+# Conventions de tests unitaires — Frontend
+
+## Outil
+Vitest (intégré nativement à Angular CLI, pas d'installation à faire).
+
+## Où écrire les tests
+Un fichier `*.spec.ts` à côté de chaque fichier testé (convention Angular CLI
+par défaut, respectée automatiquement par `ng generate`).
+
+## Structure attendue (Arrange / Act / Assert)
+```typescript
+it('devrait faire X quand Y', () => {
+ // Arrange : préparer les données et les mocks
+ const input = { valeur: 42 };
+
+ // Act : exécuter le code testé
+ const result = service.doSomething(input);
+
+ // Assert : vérifier le résultat
+ expect(result).toBe(true);
+});
+```
+
+## Ce qui doit être testé en priorité
+- Services (`core/services/`) : logique métier, gestion des erreurs
+- Guards et interceptors (`core/guards/`, `core/interceptors/`) : chaque branche de décision
+- Composants avec logique (formulaires, conditions d'affichage) — pas nécessaire pour
+ un composant 100% template, sans logique
+
+## Gabarit — tester un service avec appel HTTP
+```typescript
+import { TestBed } from '@angular/core/testing';
+import { provideHttpClient } from '@angular/common/http';
+import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
+import { MonService } from './mon.service';
+
+describe('MonService', () => {
+ let service: MonService;
+ let httpMock: HttpTestingController;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [MonService, provideHttpClient(), provideHttpClientTesting()],
+ });
+ service = TestBed.inject(MonService);
+ httpMock = TestBed.inject(HttpTestingController);
+ });
+
+ afterEach(() => httpMock.verify());
+
+ it('devrait récupérer les données', () => {
+ service.getData().subscribe();
+ const req = httpMock.expectOne('/api/v1/...');
+ expect(req.request.method).toBe('GET');
+ req.flush({ /* réponse simulée */ });
+ });
+});
+```
+
+## Gabarit — tester un composant standalone
+```typescript
+import { TestBed } from '@angular/core/testing';
+import { MonComposant } from './mon-composant';
+
+describe('MonComposant', () => {
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [MonComposant],
+ }).compileComponents();
+ });
+
+ it('devrait se créer', () => {
+ const fixture = TestBed.createComponent(MonComposant);
+ expect(fixture.componentInstance).toBeTruthy();
+ });
+});
+```
+
+## Lancer les tests
+- Développement (mode watch) : `npm test`
+- Rapport de couverture (CI) : `npm run test:ci -- --coverage`, puis ouvrir `coverage/index.html`
diff --git a/apps/frontend/angular.json b/apps/frontend/angular.json
index 3140772..ddf87a3 100644
--- a/apps/frontend/angular.json
+++ b/apps/frontend/angular.json
@@ -77,7 +77,24 @@
"defaultConfiguration": "development"
},
"test": {
- "builder": "@angular/build:unit-test"
+ "builder": "@angular/build:unit-test",
+ "options": {
+ "coverage": true,
+ "coverageReporters": [
+ "text-summary",
+ "lcov",
+ "html"
+ ],
+ "reporters": [
+ "default",
+ [
+ "junit",
+ {
+ "outputFile": "test-results/junit.xml"
+ }
+ ]
+ ]
+ }
}
}
}
diff --git a/apps/frontend/package-lock.json b/apps/frontend/package-lock.json
index 8f4408b..5ba6595 100644
--- a/apps/frontend/package-lock.json
+++ b/apps/frontend/package-lock.json
@@ -21,6 +21,7 @@
"@angular/build": "^22.1.8",
"@angular/cli": "^22.1.8",
"@angular/compiler-cli": "^22.1.0",
+ "@vitest/coverage-v8": "^4.1.11",
"jsdom": "^28.0.0",
"prettier": "^3.8.1",
"typescript": "~6.0.2",
@@ -733,6 +734,16 @@
"node": "^22.18.0 || >=24.11.0"
}
},
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
+ "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@bramus/specificity": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
@@ -3692,6 +3703,37 @@
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/@vitest/coverage-v8": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz",
+ "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^1.0.2",
+ "@vitest/utils": "4.1.11",
+ "ast-v8-to-istanbul": "^1.0.0",
+ "istanbul-lib-coverage": "^3.2.2",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-reports": "^3.2.0",
+ "magicast": "^0.5.2",
+ "obug": "^2.1.1",
+ "std-env": "^4.0.0-rc.1",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@vitest/browser": "4.1.11",
+ "vitest": "4.1.11"
+ },
+ "peerDependenciesMeta": {
+ "@vitest/browser": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@vitest/expect": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz",
@@ -3943,6 +3985,18 @@
"node": ">=12"
}
},
+ "node_modules/ast-v8-to-istanbul": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.6.tgz",
+ "integrity": "sha512-fvpl29helSO2w/z7utIbrkNXILdrLwDwAMH2I/zPKlGf5244+gf+B4cyS1sANcrPY2h+hWCGSgC8N61s/+AF9A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.31",
+ "estree-walker": "^3.0.3",
+ "js-tokens": "^10.0.0"
+ }
+ },
"node_modules/baseline-browser-mapping": {
"version": "2.11.23",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.23.tgz",
@@ -5077,6 +5131,16 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -5139,6 +5203,13 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/htmlparser2": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
@@ -5382,6 +5453,45 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/jose": {
"version": "6.2.12",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz",
@@ -5886,6 +5996,84 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
+ "node_modules/magicast": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz",
+ "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/magicast/node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/magicast/node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/magicast/node_modules/@babel/parser": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
+ "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/magicast/node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -7088,6 +7276,19 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index 7369db4..552e346 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -6,7 +6,8 @@
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
- "test": "ng test"
+ "test": "ng test",
+ "test:ci": "ng test --watch=false"
},
"private": true,
"packageManager": "npm@11.19.0",
@@ -24,6 +25,7 @@
"@angular/build": "^22.1.8",
"@angular/cli": "^22.1.8",
"@angular/compiler-cli": "^22.1.0",
+ "@vitest/coverage-v8": "^4.1.11",
"jsdom": "^28.0.0",
"prettier": "^3.8.1",
"typescript": "~6.0.2",
diff --git a/apps/frontend/test-results/junit.xml b/apps/frontend/test-results/junit.xml
new file mode 100644
index 0000000..28e5ba4
--- /dev/null
+++ b/apps/frontend/test-results/junit.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/db/README.md b/db/README.md
index e80cb3c..fd62d6f 100644
--- a/db/README.md
+++ b/db/README.md
@@ -1,6 +1,7 @@
# Base de donnees
-PostgreSQL avec l'extension TimescaleDB. Non initialise, voir le ticket dedie.
+PostgreSQL 17 avec l'extension TimescaleDB, servie en local par le service `db` du
+`docker-compose.yml` racine (image `timescale/timescaledb-ha:pg17`).
- `init` : scripts de bootstrap joues au premier demarrage du conteneur.
- `migrations` : migrations SQL versionnees.
@@ -8,3 +9,31 @@ PostgreSQL avec l'extension TimescaleDB. Non initialise, voir le ticket dedie.
Les migrations du schema applicatif expose par l'API vivent dans
`apps/backend/alembic`, pas ici.
+
+## `init` ne rejoue jamais
+
+Ces scripts sont montes sur `/docker-entrypoint-initdb.d`, dont PostgreSQL ne joue le
+contenu qu'a la toute premiere initialisation, quand `PGDATA` est vide. Modifier ou
+ajouter un script ensuite reste sans effet sur une base existante : il faut detruire
+le volume, ce que fait `make db-reset`.
+
+L'image apporte ses propres scripts dans ce dossier, et ils comptent :
+
+| Script | Origine | Role |
+|---|---|---|
+| `000_install_timescaledb.sh` | image | Cree l'extension dans `postgres`, `template1` et la base applicative, et fixe `timescaledb.telemetry_level`. |
+| `001_timescaledb_tune.sh` | image | Lance `timescaledb-tune` sur la memoire et les CPU vus par le conteneur. |
+| `010_install_timescaledb_toolkit.sh` | image | Ajoute `timescaledb_toolkit`. |
+| `100-extensions.sql` | ce depot | Declare explicitement les extensions attendues. |
+| `110-test-database.sql` | ce depot | Cree `enervision_test`, attendue par la suite de tests du backend. |
+
+D'ou deux contraintes dans `docker-compose.yml`. Nos fichiers sont **montes un par un**,
+et non par leur dossier : un montage de `./db/init` sur `/docker-entrypoint-initdb.d`
+remplacerait le dossier de l'image au lieu de s'y ajouter, et ferait disparaitre les trois
+scripts ci-dessus sans le moindre message. Ajouter un fichier ici impose donc d'ajouter
+une ligne la-bas. Et leur numerotation commence a `100` pour passer apres `010`, y compris
+en locale C ou un prefixe a deux chiffres se trierait avant.
+
+Comme un bootstrap peut toujours avoir ete saute, deux gardes le rattrapent :
+`/api/v1/health/ready` repond 503 si l'extension n'est pas chargee, et la premiere
+revision Alembic refuse de s'appliquer.
diff --git a/db/init/100-extensions.sql b/db/init/100-extensions.sql
new file mode 100644
index 0000000..2326d5d
--- /dev/null
+++ b/db/init/100-extensions.sql
@@ -0,0 +1,4 @@
+-- Piege : ce script ne rejoue qu'a la premiere initialisation, quand PGDATA est vide.
+-- Le modifier ensuite reste sans effet tant que le volume n'est pas detruit.
+
+CREATE EXTENSION IF NOT EXISTS timescaledb;
diff --git a/db/init/110-test-database.sql b/db/init/110-test-database.sql
new file mode 100644
index 0000000..0f47b63
--- /dev/null
+++ b/db/init/110-test-database.sql
@@ -0,0 +1,8 @@
+-- Contrainte : le nom de cette base est code en dur dans apps/backend/tests/conftest.py.
+-- Elle sert la suite de tests de la stack locale, pas un deploiement.
+
+CREATE DATABASE enervision_test;
+
+\connect enervision_test
+
+CREATE EXTENSION IF NOT EXISTS timescaledb;
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..d8569c9
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,47 @@
+# Piege : PGDATA de l'image timescaledb-ha vaut /home/postgres/pgdata/data, pas le chemin
+# habituel de l'image postgres. Monte ailleurs, le volume ne retient rien, sans erreur.
+# Piege : db/init est monte fichier par fichier. Monter le dossier masquerait les scripts
+# d'init de l'image, dont timescaledb-tune. Ajouter un fichier impose une ligne ici.
+
+name: enervision
+
+services:
+ db:
+ image: timescale/timescaledb-ha:pg17
+ environment:
+ POSTGRES_USER: ${POSTGRES_USER:?}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?}
+ POSTGRES_DB: ${POSTGRES_DB:?}
+ TIMESCALEDB_TELEMETRY: ${TIMESCALEDB_TELEMETRY:-off}
+ ports:
+ - "${POSTGRES_PORT:-5433}:5432"
+ volumes:
+ - pgdata:/home/postgres/pgdata/data
+ - ./db/init/100-extensions.sql:/docker-entrypoint-initdb.d/100-extensions.sql:ro
+ - ./db/init/110-test-database.sql:/docker-entrypoint-initdb.d/110-test-database.sql:ro
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
+ interval: 10s
+ timeout: 5s
+ retries: 12
+ start_period: 40s
+ restart: unless-stopped
+
+ backend:
+ build: ./apps/backend
+ depends_on:
+ db:
+ condition: service_healthy
+ environment:
+ APP_ENV: ${APP_ENV:-local}
+ APP_DEBUG: ${APP_DEBUG:-false}
+ APP_LOG_LEVEL: ${APP_LOG_LEVEL:-INFO}
+ APP_SECRET_KEY: ${APP_SECRET_KEY:?}
+ APP_CORS_ORIGINS: ${APP_CORS_ORIGINS:-http://localhost:4200}
+ DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
+ ports:
+ - "${BACKEND_PORT:-8000}:8000"
+ restart: unless-stopped
+
+volumes:
+ pgdata:
diff --git a/docs/adr/0001-postgresql-timescaledb.md b/docs/adr/0001-postgresql-timescaledb.md
new file mode 100644
index 0000000..4ed562b
--- /dev/null
+++ b/docs/adr/0001-postgresql-timescaledb.md
@@ -0,0 +1,66 @@
+# 0001 - PostgreSQL avec l'extension TimescaleDB
+
+- Statut : accepte
+- Date : 2026-09-14
+
+## Contexte
+
+EnerVision collecte, stocke et restitue des series temporelles energetiques sur une machine
+on-premise. La charge est dominee par des insertions horodatees en flux et par des lectures
+agregees sur des fenetres de temps. Airflow produira des agregations continues, Grafana lira
+les memes donnees, et l'API FastAPI les exposera.
+
+Un SGBD relationnel generaliste sait faire, mais degrade a mesure que la table de mesures
+grossit : les index se fragmentent, les balayages de fenetre deviennent couteux, et il faut
+ecrire a la main le partitionnement, la retention et les agregats pre-calcules.
+
+## Decision
+
+PostgreSQL 17 avec l'extension TimescaleDB, servie en local par l'image
+`timescale/timescaledb-ha:pg17`.
+
+PostgreSQL reste une base relationnelle standard : un seul SGBD pour les donnees metier et
+les mesures, un seul dialecte SQL, un seul pilote (`asyncpg`), et l'outillage habituel.
+TimescaleDB ajoute le partitionnement automatique, les agregations continues et les
+politiques de retention sans changer de moteur.
+
+L'image `-ha` plutot que l'image alpine : elle embarque `timescaledb_toolkit`, `postgis` et
+`pgvector`. Le toolkit porte les fonctions de comblement de trous et d'analyse de series dont
+l'ETL aura besoin, et changer d'image plus tard imposerait une reinitialisation du volume.
+
+PG17 plutot que PG18 : c'est la version la mieux couverte par Airflow et Grafana a ce jour.
+
+## Frontiere entre `db/` et `apps/backend/alembic/`
+
+C'est la regle que ce document existe surtout pour fixer.
+
+- `db/init/` : bootstrap joue **une seule fois**, a la premiere initialisation du conteneur.
+ Extensions, bases annexes. Ne rejoue jamais sur un volume existant.
+- `db/migrations/` : SQL versionne qui ne decoule pas du schema applicatif, typiquement les
+ politiques de retention et de compression TimescaleDB.
+- `apps/backend/alembic/` : le schema expose par l'API, et lui seul. C'est `Base.metadata`
+ qui fait foi.
+
+Une hypertable relevera des deux : Alembic cree la table, et le `create_hypertable()` vit
+dans la meme revision Alembic, parce que separer les deux rendrait le schema irreproductible
+depuis un seul `alembic upgrade head`.
+
+## Consequences
+
+- Le projet se lie a une extension, donc a un hebergement qui l'autorise. C'est acquis
+ puisque le deploiement est on-premise.
+- `CREATE EXTENSION` demande le superutilisateur : cela reste un acte de bootstrap, pas une
+ migration applicative.
+- Un bootstrap saute ne se voit pas au demarrage de l'API. Deux gardes couvrent ce cas :
+ `/api/v1/health/ready` repond 503 si l'extension est absente, et la premiere revision
+ Alembic refuse de s'appliquer.
+- L'image `-ha` pese environ 1 Go, a telecharger une fois par poste.
+
+## Alternatives ecartees
+
+- **PostgreSQL nu, partitionnement manuel** : faisable, mais il faudrait reecrire ce que
+ TimescaleDB fournit, et le maintenir.
+- **InfluxDB** : tres bon sur la serie temporelle, mais imposerait un second SGBD pour le
+ relationnel, donc deux dialectes, deux sauvegardes et des jointures applicatives.
+- **ClickHouse** : taille pour un volume analytique que le projet n'atteindra pas, et moins
+ a l'aise sur les ecritures unitaires frequentes du flux d'ingestion.
diff --git a/infra/README.md b/infra/README.md
index bff8fbe..4866222 100644
--- a/infra/README.md
+++ b/infra/README.md
@@ -1,6 +1,22 @@
# Infrastructure
-Provisionnement Terraform de la machine on-premise. Non initialise, voir le ticket dedie.
+Provisionnement Terraform de la machine on-premise (serveur physique, accessible en SSH).
- `terraform/modules` : modules reutilisables.
+ - `k3s` : installe un cluster k3s single-node sur une machine distante via SSH
+ (script officiel `get.k3s.io`) et rapatrie le kubeconfig en local.
- `terraform/environments/` : racines Terraform, une par environnement.
+ - `dev` : instancie le module `k3s` sur le serveur de l'ecole.
+ - `prod` : non initialise, voir le ticket dedie.
+
+## Usage (environments/dev)
+
+```bash
+cd infra/terraform/environments/dev
+cp terraform.tfvars.example terraform.tfvars # renseigner ssh_host / ssh_private_key_path
+terraform init
+terraform apply
+```
+
+Le kubeconfig est ecrit localement au chemin defini par `kubeconfig_output_path`
+(par defaut `./kubeconfig`, ignore par git).
diff --git a/infra/terraform/environments/dev/main.tf b/infra/terraform/environments/dev/main.tf
new file mode 100644
index 0000000..1361f4d
--- /dev/null
+++ b/infra/terraform/environments/dev/main.tf
@@ -0,0 +1,11 @@
+module "k3s" {
+ source = "../../modules/k3s"
+
+ ssh_host = var.ssh_host
+ ssh_port = var.ssh_port
+ ssh_user = var.ssh_user
+ ssh_private_key_path = var.ssh_private_key_path
+ k3s_version = var.k3s_version
+ k3s_disable_components = var.k3s_disable_components
+ kubeconfig_output_path = var.kubeconfig_output_path
+}
diff --git a/infra/terraform/environments/dev/outputs.tf b/infra/terraform/environments/dev/outputs.tf
new file mode 100644
index 0000000..69cd419
--- /dev/null
+++ b/infra/terraform/environments/dev/outputs.tf
@@ -0,0 +1,9 @@
+output "kubeconfig_path" {
+ description = "Chemin local du kubeconfig recupere apres installation."
+ value = module.k3s.kubeconfig_path
+}
+
+output "node_host" {
+ description = "Adresse du serveur sur lequel k3s est installe."
+ value = module.k3s.node_host
+}
diff --git a/infra/terraform/environments/dev/terraform.tfvars.example b/infra/terraform/environments/dev/terraform.tfvars.example
new file mode 100644
index 0000000..323145a
--- /dev/null
+++ b/infra/terraform/environments/dev/terraform.tfvars.example
@@ -0,0 +1,8 @@
+ssh_host = "10.0.0.10"
+ssh_port = 22
+ssh_user = "root"
+ssh_private_key_path = "~/.ssh/id_ed25519_enervision"
+# Epingler une version reelle avant apply : https://github.com/k3s-io/k3s/releases
+k3s_version = "v1.31.5+k3s1"
+k3s_disable_components = ["traefik"]
+kubeconfig_output_path = "./kubeconfig"
diff --git a/infra/terraform/environments/dev/variables.tf b/infra/terraform/environments/dev/variables.tf
new file mode 100644
index 0000000..9d507e3
--- /dev/null
+++ b/infra/terraform/environments/dev/variables.tf
@@ -0,0 +1,39 @@
+variable "ssh_host" {
+ type = string
+ description = "Adresse IP ou nom d'hote du serveur on-premise de l'ecole."
+}
+
+variable "ssh_port" {
+ type = number
+ description = "Port SSH du serveur."
+ default = 22
+}
+
+variable "ssh_user" {
+ type = string
+ description = "Utilisateur SSH utilise pour l'installation."
+ default = "root"
+}
+
+variable "ssh_private_key_path" {
+ type = string
+ description = "Chemin local vers la cle privee SSH."
+ sensitive = true
+}
+
+variable "k3s_version" {
+ type = string
+ description = "Version k3s a epingler pour un deploiement reproductible (ex: v1.31.5+k3s1). Voir https://github.com/k3s-io/k3s/releases."
+}
+
+variable "k3s_disable_components" {
+ type = list(string)
+ description = "Composants embarques k3s a desactiver."
+ default = ["traefik"]
+}
+
+variable "kubeconfig_output_path" {
+ type = string
+ description = "Chemin local ou ecrire le kubeconfig recupere apres installation."
+ default = "./kubeconfig"
+}
diff --git a/infra/terraform/environments/dev/versions.tf b/infra/terraform/environments/dev/versions.tf
new file mode 100644
index 0000000..d793df3
--- /dev/null
+++ b/infra/terraform/environments/dev/versions.tf
@@ -0,0 +1,14 @@
+terraform {
+ required_version = ">= 1.7"
+
+ required_providers {
+ null = {
+ source = "hashicorp/null"
+ version = "~> 3.2"
+ }
+ }
+
+ backend "local" {
+ path = "terraform.tfstate"
+ }
+}
diff --git a/infra/terraform/modules/k3s/main.tf b/infra/terraform/modules/k3s/main.tf
new file mode 100644
index 0000000..a8c23de
--- /dev/null
+++ b/infra/terraform/modules/k3s/main.tf
@@ -0,0 +1,55 @@
+locals {
+ sudo_prefix = var.ssh_user == "root" ? "" : "sudo "
+ install_env = "INSTALL_K3S_VERSION=${var.k3s_version} "
+ disable_flags = join(" ", [for c in var.k3s_disable_components : "--disable=${c}"])
+ kubeconfig_cmd = "${local.sudo_prefix}cat /etc/rancher/k3s/k3s.yaml"
+}
+
+resource "null_resource" "k3s_install" {
+ triggers = {
+ ssh_host = var.ssh_host
+ k3s_version = var.k3s_version
+ disable_components = join(",", var.k3s_disable_components)
+ }
+
+ connection {
+ type = "ssh"
+ host = var.ssh_host
+ port = var.ssh_port
+ user = var.ssh_user
+ private_key = file(var.ssh_private_key_path)
+ }
+
+ provisioner "remote-exec" {
+ inline = [
+ "${local.sudo_prefix}sh -c 'curl -sfL https://get.k3s.io | ${local.install_env}sh -s - server ${local.disable_flags}'",
+ "until ${local.sudo_prefix}test -f /etc/rancher/k3s/k3s.yaml; do sleep 2; done",
+ ]
+ }
+
+ # Le kubeconfig est lu via sudo (fetch_kubeconfig), pas besoin de --write-kubeconfig-mode :
+ # il reste 600/root par defaut, ce qui evite d'exposer les droits cluster-admin a tout utilisateur local.
+ provisioner "remote-exec" {
+ when = destroy
+ on_failure = continue
+ inline = [
+ "${local.sudo_prefix}sh -c 'test -x /usr/local/bin/k3s-uninstall.sh && /usr/local/bin/k3s-uninstall.sh || true'",
+ ]
+ }
+}
+
+resource "null_resource" "fetch_kubeconfig" {
+ depends_on = [null_resource.k3s_install]
+
+ triggers = {
+ install_id = null_resource.k3s_install.id
+ }
+
+ provisioner "local-exec" {
+ interpreter = ["bash", "-c"]
+ command = <<-EOT
+ ssh -i "${var.ssh_private_key_path}" -p ${var.ssh_port} -o StrictHostKeyChecking=accept-new ${var.ssh_user}@${var.ssh_host} '${local.kubeconfig_cmd}' \
+ | sed 's/127.0.0.1/${var.ssh_host}/' > "${var.kubeconfig_output_path}"
+ EOT
+ }
+}
diff --git a/infra/terraform/modules/k3s/outputs.tf b/infra/terraform/modules/k3s/outputs.tf
new file mode 100644
index 0000000..6b92aad
--- /dev/null
+++ b/infra/terraform/modules/k3s/outputs.tf
@@ -0,0 +1,9 @@
+output "kubeconfig_path" {
+ description = "Chemin local du kubeconfig recupere apres installation."
+ value = var.kubeconfig_output_path
+}
+
+output "node_host" {
+ description = "Adresse de la machine sur laquelle k3s est installe."
+ value = var.ssh_host
+}
diff --git a/infra/terraform/modules/k3s/variables.tf b/infra/terraform/modules/k3s/variables.tf
new file mode 100644
index 0000000..08c0e1c
--- /dev/null
+++ b/infra/terraform/modules/k3s/variables.tf
@@ -0,0 +1,43 @@
+variable "ssh_host" {
+ type = string
+ description = "Adresse IP ou nom d'hote de la machine on-premise cible."
+}
+
+variable "ssh_port" {
+ type = number
+ description = "Port SSH de la machine cible."
+ default = 22
+}
+
+variable "ssh_user" {
+ type = string
+ description = "Utilisateur SSH. Si different de root, les commandes d'installation sont prefixees par sudo."
+ default = "root"
+}
+
+variable "ssh_private_key_path" {
+ type = string
+ description = "Chemin local vers la cle privee SSH utilisee pour se connecter a la machine cible."
+ sensitive = true
+}
+
+variable "k3s_version" {
+ type = string
+ description = "Version k3s a epingler pour un deploiement reproductible (ex: v1.31.5+k3s1). Voir https://github.com/k3s-io/k3s/releases."
+
+ validation {
+ condition = length(trimspace(var.k3s_version)) > 0
+ error_message = "k3s_version doit etre epinglee explicitement, pas de valeur vide (sinon k3s.io installerait la derniere version a chaque run, non reproductible)."
+ }
+}
+
+variable "k3s_disable_components" {
+ type = list(string)
+ description = "Composants embarques a desactiver a l'installation (ex: traefik, servicelb)."
+ default = ["traefik"]
+}
+
+variable "kubeconfig_output_path" {
+ type = string
+ description = "Chemin local ou ecrire le kubeconfig recupere apres installation."
+}
diff --git a/infra/terraform/modules/k3s/versions.tf b/infra/terraform/modules/k3s/versions.tf
new file mode 100644
index 0000000..90c2aa0
--- /dev/null
+++ b/infra/terraform/modules/k3s/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.7"
+
+ required_providers {
+ null = {
+ source = "hashicorp/null"
+ version = "~> 3.2"
+ }
+ }
+}
diff --git a/terraform.tf b/terraform.tf
deleted file mode 100644
index 0994a4d..0000000
--- a/terraform.tf
+++ /dev/null
@@ -1,19 +0,0 @@
-# Provider Docker
-terraform {
- required_providers {
- docker = {
- source = "kreuzwerker/docker"
- version = "~> 3.0"
- }
- }
-}
-
-provider "docker" {
- host = "unix:///var/run/docker.sock"
-}
-
-# Image Docker
-resource "docker_image" "python" {
- name = "python:3.14.7"
- keep_locally = true
-}
diff --git a/test.txt b/test.txt
deleted file mode 100644
index 28d0af9..0000000
--- a/test.txt
+++ /dev/null
@@ -1 +0,0 @@
-coucou