paas-charm 2.0.dev3__tar.gz → 2.0.dev4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/PKG-INFO +1 -1
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/pyproject.toml +2 -2
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/app.py +180 -21
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/charm.py +22 -1
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/charm_state.py +72 -9
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/fastapi/app.py +4 -4
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/paas_config.py +107 -1
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/springboot/charm.py +9 -6
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm.egg-info/PKG-INFO +1 -1
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/README.md +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/setup.cfg +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/__init__.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/_gunicorn/__init__.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/_gunicorn/charm.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/_gunicorn/webserver.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/_gunicorn/workload_config.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/_gunicorn/wsgi_app.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/charm_utils.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/database_migration.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/databases.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/django/__init__.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/django/charm.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/django/cos/grafana_dashboards/django.json +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/django/cos/loki_alert_rules/.gitkeep +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/django/cos/prometheus_alert_rules/django.rule +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/exceptions.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/expressjs/__init__.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/expressjs/charm.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/expressjs/cos/grafana_dashboards/expressjs.json +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/expressjs/cos/loki_alert_rules/.gitkeep +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/expressjs/cos/prometheus_alert_rules/.gitkeep +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/fastapi/__init__.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/fastapi/charm.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/fastapi/cos/grafana_dashboards/fastapi.json +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/fastapi/cos/loki_alert_rules/.gitkeep +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/fastapi/cos/prometheus_alert_rules/.gitkeep +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/flask/__init__.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/flask/charm.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/flask/cos/grafana_dashboards/flask.json +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/flask/cos/loki_alert_rules/.gitkeep +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/flask/cos/prometheus_alert_rules/flask.rule +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/framework.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/go/__init__.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/go/charm.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/go/cos/grafana_dashboards/go.json +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/go/cos/loki_alert_rules/.gitkeep +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/go/cos/prometheus_alert_rules/.gitkeep +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/http_proxy.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/oauth.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/observability.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/peers.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/rabbitmq.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/relations.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/s3.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/saml.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/secret_key.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/springboot/__init__.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/springboot/cos/grafana_dashboards/spring_boot.json +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/springboot/cos/loki_alert_rules/.gitkeep +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/springboot/cos/prometheus_alert_rules/.gitkeep +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/templates/_gunicorn_json_logging.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/templates/fastapi/uvicorn-log-config.json +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/templates/fastapi/uvicorn_log_handler.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/templates/gunicorn.conf.py.j2 +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/tracing.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/utils.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/valkey.py +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm.egg-info/SOURCES.txt +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm.egg-info/dependency_links.txt +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm.egg-info/requires.txt +0 -0
- {paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: paas-charm
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.dev4
|
|
4
4
|
Summary: Companion library for 12-factor app support in Charmcraft & Rockcraft.
|
|
5
5
|
Author-email: Canonical IS DevOps team <is-devops-team@canonical.com>
|
|
6
6
|
Project-URL: Repository, https://github.com/canonical/paas-charm
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# See LICENSE file for licensing details.
|
|
3
3
|
[project]
|
|
4
4
|
name = "paas-charm"
|
|
5
|
-
version = "2.0.
|
|
5
|
+
version = "2.0.dev4"
|
|
6
6
|
description = "Companion library for 12-factor app support in Charmcraft & Rockcraft."
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
authors = [
|
|
@@ -40,7 +40,7 @@ where = ["src"]
|
|
|
40
40
|
|
|
41
41
|
[tool.uv.sources]
|
|
42
42
|
# opcli is published only from the charm-ci git repository, not PyPI.
|
|
43
|
-
opcli = { git = "https://github.com/canonical/charm-ci.git", tag = "v1.0.
|
|
43
|
+
opcli = { git = "https://github.com/canonical/charm-ci.git", tag = "v1.0.2" }
|
|
44
44
|
|
|
45
45
|
[tool.setuptools.package-data]
|
|
46
46
|
paas_charm = ["**/cos/**", "**/cos/**/.**", "**/*.j2", "**/templates/**/*.py", "**/templates/**/*.json"]
|
|
@@ -16,7 +16,8 @@ from dpcharmlibs.interfaces import ValkeyResponseModel
|
|
|
16
16
|
|
|
17
17
|
from paas_charm.charm_state import CharmState
|
|
18
18
|
from paas_charm.database_migration import DatabaseMigration
|
|
19
|
-
from paas_charm.
|
|
19
|
+
from paas_charm.exceptions import PaasConfigError
|
|
20
|
+
from paas_charm.paas_config import EnvVarConfig, LoggingFormat, SecretEnvVarsConfig
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
22
23
|
|
|
@@ -442,38 +443,198 @@ class App: # pylint: disable=too-many-instance-attributes
|
|
|
442
443
|
self._run_migrations()
|
|
443
444
|
self._container.replan()
|
|
444
445
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
446
|
+
def gen_environment(self) -> dict[str, str]:
|
|
447
|
+
"""Generate the final workload environment in precedence order.
|
|
448
|
+
|
|
449
|
+
Explicit config mappings override integrations, which override library-generated
|
|
450
|
+
variables. The config/framework layer comes from ``_framework_environment``.
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
The final environment with string-encoded values.
|
|
454
|
+
"""
|
|
455
|
+
framework_sources = self._framework_config_mapping_sources()
|
|
456
|
+
env = self._framework_environment()
|
|
457
|
+
for name, value in self._generate_integration_environments(
|
|
458
|
+
prefix=self.integrations_prefix
|
|
459
|
+
).items():
|
|
460
|
+
if name in env:
|
|
461
|
+
logger.warning(
|
|
462
|
+
"Environment variable %r for a relation or Prometheus metrics overwrites an "
|
|
463
|
+
"environment variable from charm configuration or framework settings",
|
|
464
|
+
name,
|
|
465
|
+
)
|
|
466
|
+
env[name] = value
|
|
467
|
+
for name, (source, value) in self._mapped_config_environment(framework_sources).items():
|
|
468
|
+
if name in env:
|
|
469
|
+
logger.warning(
|
|
470
|
+
"Explicit config.options mapping from %r overwrites environment variable %r",
|
|
471
|
+
source,
|
|
472
|
+
name,
|
|
473
|
+
)
|
|
474
|
+
env[name] = value
|
|
475
|
+
return env
|
|
476
|
+
|
|
477
|
+
def _mapped_framework_config_fields(self) -> set[str]:
|
|
478
|
+
"""Get framework fields whose default outputs are explicitly renamed.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
Framework field names, excluding unmapped secret-key entries.
|
|
482
|
+
"""
|
|
483
|
+
mappings = self._charm_state.config_options.options
|
|
484
|
+
return {
|
|
485
|
+
field
|
|
486
|
+
for option, field in self._charm_state.framework_config_fields.items()
|
|
487
|
+
if (mapping := mappings.get(option)) is not None
|
|
488
|
+
and (
|
|
489
|
+
isinstance(mapping, EnvVarConfig)
|
|
490
|
+
or (
|
|
491
|
+
field in ("secret_key", "app_secret_key")
|
|
492
|
+
and "value" in mapping.secret_env_vars
|
|
493
|
+
and not isinstance(self._charm_state.framework_config.get(field), dict)
|
|
494
|
+
)
|
|
495
|
+
)
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
def _framework_config_mapping_sources(self) -> dict[str, str | dict[str, str]]:
|
|
499
|
+
"""Read resolved framework values without borrowing unrelated generated outputs.
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
Values keyed by exact option names, using the existing encoding and key fallback.
|
|
503
|
+
"""
|
|
504
|
+
sources: dict[str, str | dict[str, str]] = {}
|
|
505
|
+
for option, mapping in self._charm_state.config_options.options.items():
|
|
506
|
+
field = self._charm_state.framework_config_fields.get(option)
|
|
507
|
+
if field is None:
|
|
508
|
+
continue
|
|
509
|
+
value = self._charm_state.framework_config.get(field)
|
|
510
|
+
if value is None and field in ("secret_key", "app_secret_key"):
|
|
511
|
+
value = self._charm_state.secret_key
|
|
512
|
+
if value is None:
|
|
513
|
+
continue
|
|
514
|
+
if isinstance(mapping, SecretEnvVarsConfig):
|
|
515
|
+
if isinstance(value, dict):
|
|
516
|
+
sources[option] = value
|
|
517
|
+
elif field in ("secret_key", "app_secret_key"):
|
|
518
|
+
sources[option] = {"value": encode_env(value)}
|
|
519
|
+
else:
|
|
520
|
+
sources[option] = encode_env(value)
|
|
521
|
+
else:
|
|
522
|
+
sources[option] = encode_env(value)
|
|
523
|
+
return sources
|
|
524
|
+
|
|
525
|
+
def _mapped_config_environment(
|
|
526
|
+
self, framework_sources: dict[str, str | dict[str, str]]
|
|
527
|
+
) -> dict[str, tuple[str, str]]:
|
|
528
|
+
"""Generate explicit config destinations from their original source values.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
framework_sources: Resolved framework outputs keyed by exact option names,
|
|
532
|
+
including secret-key fallbacks.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
Destinations mapped to source names and encoded values, omitting absent values.
|
|
536
|
+
|
|
537
|
+
Raises:
|
|
538
|
+
PaasConfigError: If a secret mapping receives non-secret content.
|
|
539
|
+
"""
|
|
540
|
+
env: dict[str, tuple[str, str]] = {}
|
|
541
|
+
for option, mapping in self._charm_state.config_options.options.items():
|
|
542
|
+
value = (
|
|
543
|
+
framework_sources.get(option)
|
|
544
|
+
if option in self._charm_state.framework_config_fields
|
|
545
|
+
else self._charm_state.user_defined_config.get(option.replace("-", "_"))
|
|
546
|
+
)
|
|
547
|
+
if value is None:
|
|
548
|
+
continue
|
|
549
|
+
if isinstance(mapping, SecretEnvVarsConfig):
|
|
550
|
+
if not isinstance(value, collections.abc.Mapping):
|
|
551
|
+
raise PaasConfigError(
|
|
552
|
+
f"config.options.{option}.secret-env-vars requires secret content"
|
|
553
|
+
)
|
|
554
|
+
for key, destination in mapping.secret_env_vars.items():
|
|
555
|
+
if key not in value:
|
|
556
|
+
logger.warning(
|
|
557
|
+
"Charm configuration option %r has no secret content key %r; "
|
|
558
|
+
"skipping mapped environment variable %r",
|
|
559
|
+
option,
|
|
560
|
+
key,
|
|
561
|
+
destination,
|
|
562
|
+
)
|
|
563
|
+
continue
|
|
564
|
+
env[destination] = (f"{option}.{key}", encode_env(value[key]))
|
|
565
|
+
else:
|
|
566
|
+
env[mapping.env_var] = (option, encode_env(value))
|
|
567
|
+
return env
|
|
568
|
+
|
|
569
|
+
def _unmapped_framework_config(self) -> dict[str, str | int | bool | dict[str, str]]:
|
|
570
|
+
"""Keep framework defaults and secret entries that have not been renamed.
|
|
460
571
|
|
|
461
572
|
Returns:
|
|
462
|
-
|
|
573
|
+
Resolved framework configuration with mapped fields or secret entries omitted.
|
|
574
|
+
"""
|
|
575
|
+
mapped_fields = self._mapped_framework_config_fields()
|
|
576
|
+
config = {
|
|
577
|
+
field: value
|
|
578
|
+
for field, value in self._charm_state.framework_config.items()
|
|
579
|
+
if field not in mapped_fields
|
|
580
|
+
}
|
|
581
|
+
for option, field in self._charm_state.framework_config_fields.items():
|
|
582
|
+
mapping = self._charm_state.config_options.options.get(option)
|
|
583
|
+
value = config.get(field)
|
|
584
|
+
if (
|
|
585
|
+
isinstance(mapping, SecretEnvVarsConfig)
|
|
586
|
+
and isinstance(value, dict)
|
|
587
|
+
and value.keys() & mapping.secret_env_vars.keys()
|
|
588
|
+
):
|
|
589
|
+
remaining = {
|
|
590
|
+
key: val for key, val in value.items() if key not in mapping.secret_env_vars
|
|
591
|
+
}
|
|
592
|
+
if remaining:
|
|
593
|
+
config[field] = remaining
|
|
594
|
+
else:
|
|
595
|
+
config.pop(field)
|
|
596
|
+
return config
|
|
597
|
+
|
|
598
|
+
def _framework_environment(self) -> dict[str, str]: # noqa: too-complex
|
|
599
|
+
"""Build config and library-owned variables before higher-priority overrides.
|
|
600
|
+
|
|
601
|
+
Explicitly mapped sources are omitted from their default names. Unmapped config
|
|
602
|
+
retains its existing prefixes and value encoding. Framework subclasses extend
|
|
603
|
+
this layer; ``gen_environment`` then applies relations and explicit mappings.
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
The config/framework environment with string-encoded values.
|
|
463
607
|
"""
|
|
464
608
|
prefix = self.configuration_prefix
|
|
465
609
|
env = {}
|
|
610
|
+
mappings = {
|
|
611
|
+
option.replace("-", "_"): mapping
|
|
612
|
+
for option, mapping in self._charm_state.config_options.options.items()
|
|
613
|
+
}
|
|
466
614
|
for app_config_key, app_config_value in self._charm_state.user_defined_config.items():
|
|
615
|
+
mapping = mappings.get(app_config_key)
|
|
616
|
+
if app_config_key in self._charm_state.framework_config_field_names or isinstance(
|
|
617
|
+
mapping, EnvVarConfig
|
|
618
|
+
):
|
|
619
|
+
continue
|
|
620
|
+
if isinstance(mapping, SecretEnvVarsConfig) and app_config_value is None:
|
|
621
|
+
continue
|
|
622
|
+
mapped_keys = (
|
|
623
|
+
mapping.secret_env_vars if isinstance(mapping, SecretEnvVarsConfig) else {}
|
|
624
|
+
)
|
|
467
625
|
if isinstance(app_config_value, collections.abc.Mapping):
|
|
468
626
|
for k, v in app_config_value.items():
|
|
627
|
+
if k in mapped_keys:
|
|
628
|
+
continue
|
|
469
629
|
env[f"{prefix}{app_config_key.upper()}_{k.replace('-', '_').upper()}"] = (
|
|
470
630
|
encode_env(v)
|
|
471
631
|
)
|
|
472
632
|
else:
|
|
473
633
|
env[f"{prefix}{app_config_key.upper()}"] = encode_env(app_config_value)
|
|
474
634
|
|
|
475
|
-
framework_config = self.
|
|
635
|
+
framework_config = self._unmapped_framework_config()
|
|
476
636
|
framework_config_prefix = self.framework_config_prefix
|
|
637
|
+
mapped_fields = self._mapped_framework_config_fields()
|
|
477
638
|
env.update(
|
|
478
639
|
{
|
|
479
640
|
f"{framework_config_prefix}{k.upper()}": encode_env(v)
|
|
@@ -488,7 +649,7 @@ class App: # pylint: disable=too-many-instance-attributes
|
|
|
488
649
|
if self._charm_state.base_url:
|
|
489
650
|
env[f"{prefix}BASE_URL"] = self._charm_state.base_url
|
|
490
651
|
secret_key_env = f"{prefix}SECRET_KEY"
|
|
491
|
-
if secret_key_env not in env:
|
|
652
|
+
if secret_key_env not in env and not {"secret_key", "app_secret_key"} & mapped_fields:
|
|
492
653
|
env[secret_key_env] = self._charm_state.secret_key
|
|
493
654
|
for proxy_variable in ("http_proxy", "https_proxy", "no_proxy"):
|
|
494
655
|
proxy_value = getattr(self._charm_state.proxy, proxy_variable)
|
|
@@ -499,8 +660,6 @@ class App: # pylint: disable=too-many-instance-attributes
|
|
|
499
660
|
if self._charm_state.peer_fqdns is not None:
|
|
500
661
|
env[f"{prefix}PEER_FQDNS"] = self._charm_state.peer_fqdns
|
|
501
662
|
|
|
502
|
-
env.update(self._generate_integration_environments(prefix=self.integrations_prefix))
|
|
503
|
-
|
|
504
663
|
return env
|
|
505
664
|
|
|
506
665
|
def _generate_integration_environments(self, prefix: str = "") -> dict[str, str]:
|
|
@@ -18,7 +18,12 @@ from ops.model import Container
|
|
|
18
18
|
from pydantic import BaseModel, ValidationError
|
|
19
19
|
|
|
20
20
|
from paas_charm.app import App, WorkloadConfig
|
|
21
|
-
from paas_charm.charm_state import
|
|
21
|
+
from paas_charm.charm_state import (
|
|
22
|
+
CharmState,
|
|
23
|
+
IntegrationRequirers,
|
|
24
|
+
framework_config_option_fields,
|
|
25
|
+
is_user_defined_config,
|
|
26
|
+
)
|
|
22
27
|
from paas_charm.charm_utils import block_if_invalid_data
|
|
23
28
|
from paas_charm.database_migration import DatabaseMigration, DatabaseMigrationStatus
|
|
24
29
|
from paas_charm.databases import make_database_requirers
|
|
@@ -32,6 +37,7 @@ from paas_charm.oauth import PaaSOAuthRequirer
|
|
|
32
37
|
from paas_charm.observability import Observability
|
|
33
38
|
from paas_charm.paas_config import (
|
|
34
39
|
FRAMEWORKS_SUPPORTING_LOGGING_FORMAT,
|
|
40
|
+
ConfigOptions,
|
|
35
41
|
LoggingFormat,
|
|
36
42
|
read_paas_config,
|
|
37
43
|
)
|
|
@@ -45,6 +51,7 @@ from paas_charm.tracing import PaaSTracingEndpointRequirer
|
|
|
45
51
|
from paas_charm.utils import (
|
|
46
52
|
build_validation_error_message,
|
|
47
53
|
config_get_with_secret,
|
|
54
|
+
config_metadata,
|
|
48
55
|
get_endpoints_by_interface_name,
|
|
49
56
|
merge_cos_directories,
|
|
50
57
|
)
|
|
@@ -96,6 +103,19 @@ class PaasCharm(abc.ABC, ops.CharmBase): # pylint: disable=too-many-instance-at
|
|
|
96
103
|
super().__init__(framework)
|
|
97
104
|
self._framework_name = framework_name
|
|
98
105
|
self._paas_config = read_paas_config()
|
|
106
|
+
config_options: ConfigOptions = self._paas_config.config
|
|
107
|
+
if config_options.options: # pylint: disable=no-member
|
|
108
|
+
options = config_metadata(pathlib.Path(self.charm_dir))["options"]
|
|
109
|
+
framework_fields = framework_config_option_fields(
|
|
110
|
+
self.framework_config_class, options.keys()
|
|
111
|
+
)
|
|
112
|
+
unsupported = {
|
|
113
|
+
option
|
|
114
|
+
for option in options
|
|
115
|
+
if not is_user_defined_config(option, framework_name)
|
|
116
|
+
and option not in framework_fields
|
|
117
|
+
}
|
|
118
|
+
config_options.validate_sources(options, unsupported) # pylint: disable=no-member
|
|
99
119
|
|
|
100
120
|
self._secret_key = SecretKeyStorage(charm=self, label="app-secret-key")
|
|
101
121
|
self._peers = Peers(charm=self)
|
|
@@ -769,6 +789,7 @@ class PaasCharm(abc.ABC, ops.CharmBase): # pylint: disable=too-many-instance-at
|
|
|
769
789
|
config=config,
|
|
770
790
|
framework=self._framework_name,
|
|
771
791
|
framework_config=self.get_framework_config(),
|
|
792
|
+
config_options=self._paas_config.config,
|
|
772
793
|
secret_key=self._secret_key,
|
|
773
794
|
peers=self._peers,
|
|
774
795
|
integration_requirers=IntegrationRequirers(
|
|
@@ -9,13 +9,14 @@ import pathlib
|
|
|
9
9
|
import typing
|
|
10
10
|
from dataclasses import dataclass, field
|
|
11
11
|
|
|
12
|
-
from pydantic import BaseModel, Field, ValidationError, create_model
|
|
12
|
+
from pydantic import AliasChoices, BaseModel, Field, ValidationError, create_model
|
|
13
13
|
|
|
14
14
|
from paas_charm.exceptions import (
|
|
15
15
|
CharmConfigInvalidError,
|
|
16
16
|
InvalidRelationDataError,
|
|
17
17
|
RelationDataError,
|
|
18
18
|
)
|
|
19
|
+
from paas_charm.paas_config import ConfigOptions
|
|
19
20
|
from paas_charm.peers import Peers
|
|
20
21
|
from paas_charm.relations import CustomRelation
|
|
21
22
|
from paas_charm.secret_key import SecretKeyStorage
|
|
@@ -50,6 +51,9 @@ class CharmState: # pylint: disable=too-many-instance-attributes
|
|
|
50
51
|
secret_key: the charm managed application secret key.
|
|
51
52
|
is_secret_key_ready: whether the application secret key is ready.
|
|
52
53
|
proxy: proxy information.
|
|
54
|
+
config_options: Settings for existing charm configuration options.
|
|
55
|
+
framework_config_fields: Accepted framework config input names mapped to model fields.
|
|
56
|
+
framework_config_field_names: Framework fields protected from implicit user config outputs.
|
|
53
57
|
"""
|
|
54
58
|
|
|
55
59
|
def __init__( # pylint: disable=too-many-arguments
|
|
@@ -58,12 +62,15 @@ class CharmState: # pylint: disable=too-many-instance-attributes
|
|
|
58
62
|
framework: str,
|
|
59
63
|
is_secret_key_ready: bool,
|
|
60
64
|
user_defined_config: dict[str, int | str | bool | dict[str, str]] | None = None,
|
|
61
|
-
framework_config: dict[str, int | str] | None = None,
|
|
65
|
+
framework_config: dict[str, int | str | dict[str, str]] | None = None,
|
|
62
66
|
secret_key: str | None = None,
|
|
63
67
|
peer_fqdns: str | None = None,
|
|
64
68
|
integrations: "IntegrationsState | None" = None,
|
|
65
69
|
base_url: str | None = None,
|
|
70
|
+
config_options: ConfigOptions | None = None,
|
|
71
|
+
framework_config_fields: dict[str, str] | None = None,
|
|
66
72
|
custom_relations: list[CustomRelation] | None = None,
|
|
73
|
+
framework_config_field_names: set[str] | None = None,
|
|
67
74
|
):
|
|
68
75
|
"""Initialize a new instance of the CharmState class.
|
|
69
76
|
|
|
@@ -76,7 +83,10 @@ class CharmState: # pylint: disable=too-many-instance-attributes
|
|
|
76
83
|
peer_fqdns: The FQDN of units in the peer relation.
|
|
77
84
|
integrations: Information about the integrations.
|
|
78
85
|
base_url: Base URL for the service.
|
|
86
|
+
config_options: Settings for existing charm configuration options.
|
|
87
|
+
framework_config_fields: Accepted framework config input names mapped to model fields.
|
|
79
88
|
custom_relations: Custom relations.
|
|
89
|
+
framework_config_field_names: Framework fields protected from implicit user outputs.
|
|
80
90
|
"""
|
|
81
91
|
self.framework = framework
|
|
82
92
|
self._framework_config = framework_config if framework_config is not None else {}
|
|
@@ -86,6 +96,9 @@ class CharmState: # pylint: disable=too-many-instance-attributes
|
|
|
86
96
|
self.peer_fqdns = peer_fqdns
|
|
87
97
|
self.integrations = integrations or IntegrationsState()
|
|
88
98
|
self.base_url = base_url
|
|
99
|
+
self.config_options = config_options if config_options is not None else ConfigOptions()
|
|
100
|
+
self.framework_config_fields = framework_config_fields or {}
|
|
101
|
+
self.framework_config_field_names = framework_config_field_names or set()
|
|
89
102
|
self.custom_relations = custom_relations or []
|
|
90
103
|
|
|
91
104
|
@classmethod
|
|
@@ -101,6 +114,7 @@ class CharmState: # pylint: disable=too-many-instance-attributes
|
|
|
101
114
|
integration_requirers: "IntegrationRequirers",
|
|
102
115
|
custom_relations: list[CustomRelation] | None = None,
|
|
103
116
|
base_url: str | None = None,
|
|
117
|
+
config_options: ConfigOptions | None = None,
|
|
104
118
|
) -> "CharmState":
|
|
105
119
|
"""Initialize a new instance of the CharmState class from the associated charm.
|
|
106
120
|
|
|
@@ -114,6 +128,7 @@ class CharmState: # pylint: disable=too-many-instance-attributes
|
|
|
114
128
|
integration_requirers: The collection of integration requirers.
|
|
115
129
|
custom_relations: Custom relations.
|
|
116
130
|
base_url: Base URL for the service.
|
|
131
|
+
config_options: Settings for existing charm configuration options.
|
|
117
132
|
|
|
118
133
|
Return:
|
|
119
134
|
The CharmState instance created by the provided charm.
|
|
@@ -122,16 +137,22 @@ class CharmState: # pylint: disable=too-many-instance-attributes
|
|
|
122
137
|
CharmConfigInvalidError: If some parameter in invalid.
|
|
123
138
|
RelationDataError: When relation data is either unavailable, invalid or not usable.
|
|
124
139
|
"""
|
|
140
|
+
framework_fields = framework_config_option_fields(type(framework_config), config.keys())
|
|
141
|
+
for option, field_name in framework_config_option_fields(
|
|
142
|
+
type(framework_config), config_metadata(pathlib.Path(charm_dir))["options"]
|
|
143
|
+
).items():
|
|
144
|
+
if field_name not in framework_fields.values():
|
|
145
|
+
framework_fields[option] = field_name
|
|
146
|
+
framework_options = set(framework_fields)
|
|
125
147
|
user_defined_config = {
|
|
126
148
|
k.replace("-", "_"): v
|
|
127
149
|
for k, v in config.items()
|
|
128
|
-
if is_user_defined_config(k, framework)
|
|
129
|
-
}
|
|
130
|
-
user_defined_config = {
|
|
131
|
-
k: v for k, v in user_defined_config.items() if k not in framework_config.dict().keys()
|
|
150
|
+
if is_user_defined_config(k, framework) and k not in framework_options
|
|
132
151
|
}
|
|
133
152
|
|
|
134
|
-
app_config_class = app_config_class_factory(
|
|
153
|
+
app_config_class = app_config_class_factory(
|
|
154
|
+
charm_dir, framework, framework_options=framework_options
|
|
155
|
+
)
|
|
135
156
|
try:
|
|
136
157
|
app_config_class(**user_defined_config)
|
|
137
158
|
except ValidationError as exc:
|
|
@@ -230,6 +251,9 @@ class CharmState: # pylint: disable=too-many-instance-attributes
|
|
|
230
251
|
secret_key=(secret_key.get_secret_key() if secret_key.is_ready else None),
|
|
231
252
|
is_secret_key_ready=secret_key.is_ready,
|
|
232
253
|
peer_fqdns=peer_fqdns,
|
|
254
|
+
config_options=config_options,
|
|
255
|
+
framework_config_fields=framework_fields,
|
|
256
|
+
framework_config_field_names=set(type(framework_config).model_fields),
|
|
233
257
|
integrations=integrations,
|
|
234
258
|
base_url=base_url,
|
|
235
259
|
custom_relations=custom_relations,
|
|
@@ -256,7 +280,7 @@ class CharmState: # pylint: disable=too-many-instance-attributes
|
|
|
256
280
|
)
|
|
257
281
|
|
|
258
282
|
@property
|
|
259
|
-
def framework_config(self) -> dict[str, str | int | bool]:
|
|
283
|
+
def framework_config(self) -> dict[str, str | int | bool | dict[str, str]]:
|
|
260
284
|
"""Get the value of the framework application specific configuration.
|
|
261
285
|
|
|
262
286
|
Returns:
|
|
@@ -413,12 +437,19 @@ def _create_config_attribute(option_name: str, option: dict) -> tuple[str, tuple
|
|
|
413
437
|
return (option_name, type_tuple)
|
|
414
438
|
|
|
415
439
|
|
|
416
|
-
def app_config_class_factory(
|
|
440
|
+
def app_config_class_factory(
|
|
441
|
+
charm_dir: pathlib.Path,
|
|
442
|
+
framework: str,
|
|
443
|
+
*,
|
|
444
|
+
framework_options: set[str] | None = None,
|
|
445
|
+
) -> type[BaseModel]:
|
|
417
446
|
"""App config class factory.
|
|
418
447
|
|
|
419
448
|
Args:
|
|
420
449
|
charm_dir: The charm directory.
|
|
421
450
|
framework: The framework name.
|
|
451
|
+
framework_options: Exact framework-owned option names to exclude.
|
|
452
|
+
If omitted, only reserved prefixes are excluded.
|
|
422
453
|
|
|
423
454
|
Returns:
|
|
424
455
|
Constructed app config class.
|
|
@@ -428,11 +459,43 @@ def app_config_class_factory(charm_dir: pathlib.Path, framework: str) -> type[Ba
|
|
|
428
459
|
_create_config_attribute(option_name, config_options[option_name])
|
|
429
460
|
for option_name in config_options
|
|
430
461
|
if is_user_defined_config(option_name, framework)
|
|
462
|
+
and (framework_options is None or option_name not in framework_options)
|
|
431
463
|
)
|
|
432
464
|
# mypy doesn't like the model_attributes dict
|
|
433
465
|
return create_model("AppConfig", **model_attributes) # type: ignore[call-overload]
|
|
434
466
|
|
|
435
467
|
|
|
468
|
+
def framework_config_option_fields(
|
|
469
|
+
framework_config_class: type[BaseModel], options: typing.Collection[str]
|
|
470
|
+
) -> dict[str, str]:
|
|
471
|
+
"""Map accepted flat config inputs to their framework model fields.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
framework_config_class: Framework configuration model class.
|
|
475
|
+
options: Available charm configuration option names.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
Exact input names selected according to the model's alias validation rules.
|
|
479
|
+
"""
|
|
480
|
+
config = framework_config_class.model_config
|
|
481
|
+
allow_name = config.get("validate_by_name", config.get("populate_by_name", False))
|
|
482
|
+
fields: dict[str, str] = {}
|
|
483
|
+
for field_name, model_field in framework_config_class.model_fields.items():
|
|
484
|
+
names: list[str] = []
|
|
485
|
+
alias = model_field.validation_alias
|
|
486
|
+
if config.get("validate_by_alias") is not False:
|
|
487
|
+
if isinstance(alias, str):
|
|
488
|
+
names.append(alias)
|
|
489
|
+
elif isinstance(alias, AliasChoices):
|
|
490
|
+
names.extend(name for name in alias.choices if isinstance(name, str))
|
|
491
|
+
if alias is None or allow_name:
|
|
492
|
+
names.append(field_name)
|
|
493
|
+
source = next((name for name in names if name in options), None)
|
|
494
|
+
if source is not None:
|
|
495
|
+
fields[source] = field_name
|
|
496
|
+
return fields
|
|
497
|
+
|
|
498
|
+
|
|
436
499
|
def is_user_defined_config(option_name: str, framework: str) -> bool:
|
|
437
500
|
"""Check if a config option is user defined.
|
|
438
501
|
|
|
@@ -78,13 +78,13 @@ class FastAPIApp(App):
|
|
|
78
78
|
)
|
|
79
79
|
logger.debug("Pushed %s to container", filename)
|
|
80
80
|
|
|
81
|
-
def
|
|
82
|
-
"""
|
|
81
|
+
def _framework_environment(self) -> dict[str, str]:
|
|
82
|
+
"""Build the config/framework layer, including structured logging variables.
|
|
83
83
|
|
|
84
84
|
Returns:
|
|
85
|
-
|
|
85
|
+
Config and library-owned variables before relations and explicit mappings.
|
|
86
86
|
"""
|
|
87
|
-
env = super().
|
|
87
|
+
env = super()._framework_environment()
|
|
88
88
|
if self._workload_config.logging_format == LoggingFormat.JSON:
|
|
89
89
|
existing = env.get("PYTHONPATH", "")
|
|
90
90
|
env["PYTHONPATH"] = (
|
|
@@ -153,6 +153,110 @@ class PrometheusConfig(BaseModel):
|
|
|
153
153
|
return self
|
|
154
154
|
|
|
155
155
|
|
|
156
|
+
class EnvVarConfig(BaseModel):
|
|
157
|
+
"""Environment variable name for a non-secret charm configuration option.
|
|
158
|
+
|
|
159
|
+
Attributes:
|
|
160
|
+
env_var: Complete environment variable name.
|
|
161
|
+
model_config: Pydantic model configuration.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
env_var: str = Field(alias="env-var")
|
|
165
|
+
model_config = ConfigDict(extra="forbid", strict=True)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class SecretEnvVarsConfig(BaseModel):
|
|
169
|
+
"""Environment variable names for a secret charm configuration option.
|
|
170
|
+
|
|
171
|
+
Attributes:
|
|
172
|
+
secret_env_vars: Secret content keys mapped to complete environment variable names.
|
|
173
|
+
model_config: Pydantic model configuration.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
secret_env_vars: dict[str, str] = Field(alias="secret-env-vars")
|
|
177
|
+
model_config = ConfigDict(extra="forbid", strict=True)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class ConfigOptions(BaseModel):
|
|
181
|
+
"""Settings for existing charm configuration options.
|
|
182
|
+
|
|
183
|
+
Attributes:
|
|
184
|
+
options: Exact charm configuration option names mapped to their settings.
|
|
185
|
+
model_config: Pydantic model configuration.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
options: dict[str, EnvVarConfig | SecretEnvVarsConfig] = Field(default_factory=dict)
|
|
189
|
+
model_config = ConfigDict(extra="forbid", strict=True)
|
|
190
|
+
|
|
191
|
+
@model_validator(mode="after")
|
|
192
|
+
def validate_destinations(self) -> "ConfigOptions":
|
|
193
|
+
"""Validate destination names and reject ambiguous mappings.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
The validated configuration.
|
|
197
|
+
|
|
198
|
+
Raises:
|
|
199
|
+
ValueError: If a destination is invalid or used by multiple sources.
|
|
200
|
+
"""
|
|
201
|
+
destinations: dict[str, str] = {}
|
|
202
|
+
for option, mapping in self.options.items(): # pylint: disable=no-member
|
|
203
|
+
entries = (
|
|
204
|
+
mapping.secret_env_vars.items()
|
|
205
|
+
if isinstance(mapping, SecretEnvVarsConfig)
|
|
206
|
+
else [(None, mapping.env_var)]
|
|
207
|
+
)
|
|
208
|
+
for key, destination in entries:
|
|
209
|
+
source = option if key is None else f"{option}.{key}"
|
|
210
|
+
if not destination or "\0" in destination or "=" in destination:
|
|
211
|
+
raise ValueError(
|
|
212
|
+
f"Invalid environment variable name for {source!r} in {CONFIG_FILE_NAME}: "
|
|
213
|
+
"destination names must be non-empty and contain neither '=' nor NUL"
|
|
214
|
+
)
|
|
215
|
+
if destination in destinations:
|
|
216
|
+
raise ValueError(
|
|
217
|
+
f"Environment variable {destination!r} is mapped from both "
|
|
218
|
+
f"{destinations[destination]!r} and {source!r}"
|
|
219
|
+
)
|
|
220
|
+
destinations[destination] = source
|
|
221
|
+
return self
|
|
222
|
+
|
|
223
|
+
def validate_sources(self, options: dict, unsupported: set[str]) -> None:
|
|
224
|
+
"""Validate sources against charm configuration metadata.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
options: Charm configuration option metadata.
|
|
228
|
+
unsupported: Option names without mappable environment outputs.
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
PaasConfigError: If an option is unknown, unsupported, or has the wrong mapping shape.
|
|
232
|
+
"""
|
|
233
|
+
for option, mapping in self.options.items(): # pylint: disable=no-member
|
|
234
|
+
if option not in options:
|
|
235
|
+
raise PaasConfigError(
|
|
236
|
+
f"{CONFIG_FILE_NAME}: config.options references unknown "
|
|
237
|
+
f"charm configuration option {option!r}"
|
|
238
|
+
)
|
|
239
|
+
if option in unsupported:
|
|
240
|
+
raise PaasConfigError(
|
|
241
|
+
f"{CONFIG_FILE_NAME}: config.options cannot map option {option!r}: "
|
|
242
|
+
"it has no configuration-derived environment output"
|
|
243
|
+
)
|
|
244
|
+
normalized = option.replace("-", "_")
|
|
245
|
+
for other in options:
|
|
246
|
+
if other != option and other.replace("-", "_") == normalized:
|
|
247
|
+
raise PaasConfigError(
|
|
248
|
+
f"{CONFIG_FILE_NAME}: config.options cannot distinguish charm configuration "
|
|
249
|
+
f"options {option!r} and {other!r}: hyphens and underscores are "
|
|
250
|
+
"treated identically internally"
|
|
251
|
+
)
|
|
252
|
+
is_secret = options[option]["type"] == "secret"
|
|
253
|
+
if is_secret != isinstance(mapping, SecretEnvVarsConfig):
|
|
254
|
+
expected = "secret-env-vars" if is_secret else "env-var"
|
|
255
|
+
raise PaasConfigError(
|
|
256
|
+
f"{CONFIG_FILE_NAME}: config.options.{option} must use {expected!r}"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
156
260
|
class PaasConfig(BaseModel):
|
|
157
261
|
"""Configuration from paas-config.yaml file.
|
|
158
262
|
|
|
@@ -165,11 +269,13 @@ class PaasConfig(BaseModel):
|
|
|
165
269
|
port: Optional override for the port on which the application server listens.
|
|
166
270
|
metrics_port: Optional override for the port on which the application serves metrics.
|
|
167
271
|
metrics_path: Optional override for the path on which the application serves metrics.
|
|
272
|
+
config: Settings for existing charm configuration options.
|
|
168
273
|
"""
|
|
169
274
|
|
|
170
275
|
prometheus: PrometheusConfig | None = Field(
|
|
171
276
|
default=None, description="Prometheus configuration"
|
|
172
277
|
)
|
|
278
|
+
config: ConfigOptions = Field(default_factory=ConfigOptions)
|
|
173
279
|
framework_logging_format: LoggingFormat = Field(
|
|
174
280
|
default=LoggingFormat.NONE,
|
|
175
281
|
description="Structured logging format for the framework server (e.g. 'json').",
|
|
@@ -273,7 +379,7 @@ def read_paas_config(charm_root: pathlib.Path | None = None) -> PaasConfig:
|
|
|
273
379
|
try:
|
|
274
380
|
return PaasConfig(**config_data)
|
|
275
381
|
except ValidationError as exc:
|
|
276
|
-
error_details = build_validation_error_message(exc
|
|
382
|
+
error_details = build_validation_error_message(exc)
|
|
277
383
|
error_msg = f"Invalid {CONFIG_FILE_NAME}: {error_details.short}"
|
|
278
384
|
logger.error("%s: %s", error_msg, error_details.long)
|
|
279
385
|
raise PaasConfigError(error_msg) from exc
|
|
@@ -360,20 +360,23 @@ class SpringBootApp(App):
|
|
|
360
360
|
generate_prometheus_env = staticmethod(generate_prometheus_env)
|
|
361
361
|
generate_oauth_env = staticmethod(generate_oauth_env)
|
|
362
362
|
|
|
363
|
-
def
|
|
364
|
-
"""
|
|
363
|
+
def _framework_environment(self) -> dict[str, str]:
|
|
364
|
+
"""Build the config/framework layer with Spring Boot-specific settings.
|
|
365
365
|
|
|
366
|
-
|
|
366
|
+
Relation outputs and explicit mappings are applied afterward.
|
|
367
367
|
|
|
368
368
|
Returns:
|
|
369
|
-
|
|
369
|
+
Config and library-owned variables before relations and explicit mappings.
|
|
370
370
|
"""
|
|
371
|
-
env = super().
|
|
371
|
+
env = super()._framework_environment()
|
|
372
372
|
env.pop("METRICS_PORT", None)
|
|
373
373
|
env.pop("METRICS_PATH", None)
|
|
374
374
|
# Name of the profiles field in SpringBootConfig
|
|
375
375
|
profiles_field = "app_profiles"
|
|
376
|
-
if
|
|
376
|
+
if (
|
|
377
|
+
profiles_field in self._charm_state.framework_config
|
|
378
|
+
and profiles_field not in self._mapped_framework_config_fields()
|
|
379
|
+
):
|
|
377
380
|
env["spring.profiles.active"] = str(self._charm_state.framework_config[profiles_field])
|
|
378
381
|
# Required because of the strip prefix in the ingress configuration.
|
|
379
382
|
env["server.forward-headers-strategy"] = "framework"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: paas-charm
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.dev4
|
|
4
4
|
Summary: Companion library for 12-factor app support in Charmcraft & Rockcraft.
|
|
5
5
|
Author-email: Canonical IS DevOps team <is-devops-team@canonical.com>
|
|
6
6
|
Project-URL: Repository, https://github.com/canonical/paas-charm
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/django/cos/grafana_dashboards/django.json
RENAMED
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/django/cos/loki_alert_rules/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/expressjs/cos/loki_alert_rules/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/fastapi/cos/loki_alert_rules/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/flask/cos/grafana_dashboards/flask.json
RENAMED
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/flask/cos/loki_alert_rules/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/go/cos/grafana_dashboards/go.json
RENAMED
|
File without changes
|
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/go/cos/prometheus_alert_rules/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/springboot/cos/loki_alert_rules/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/templates/_gunicorn_json_logging.py
RENAMED
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/templates/fastapi/uvicorn-log-config.json
RENAMED
|
File without changes
|
{paas_charm-2.0.dev3 → paas_charm-2.0.dev4}/src/paas_charm/templates/fastapi/uvicorn_log_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|