qontract-reconcile 0.10.1rc879__py3-none-any.whl → 0.10.1rc894__py3-none-any.whl
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.
- {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/RECORD +291 -284
- reconcile/acs_rbac.py +1 -2
- reconcile/aus/advanced_upgrade_service.py +14 -14
- reconcile/aus/aus_label_source.py +1 -2
- reconcile/aus/base.py +23 -26
- reconcile/aus/cluster_version_data.py +4 -4
- reconcile/aus/models.py +2 -3
- reconcile/aus/version_gate_approver.py +2 -6
- reconcile/aus/version_gates/__init__.py +1 -3
- reconcile/aus/version_gates/sts_version_gate_handler.py +2 -3
- reconcile/aws_account_manager/integration.py +9 -14
- reconcile/aws_account_manager/reconciler.py +51 -1
- reconcile/aws_account_manager/utils.py +3 -0
- reconcile/aws_ami_cleanup/integration.py +3 -4
- reconcile/aws_iam_password_reset.py +2 -5
- reconcile/aws_version_sync/integration.py +2 -2
- reconcile/blackbox_exporter_endpoint_monitoring.py +2 -5
- reconcile/change_owners/approver.py +4 -5
- reconcile/change_owners/bundle.py +20 -22
- reconcile/change_owners/change_types.py +23 -24
- reconcile/change_owners/changes.py +13 -16
- reconcile/change_owners/decision.py +2 -5
- reconcile/change_owners/diff.py +11 -15
- reconcile/change_owners/self_service_roles.py +1 -2
- reconcile/change_owners/tester.py +7 -10
- reconcile/checkpoint.py +2 -5
- reconcile/cli.py +26 -12
- reconcile/closedbox_endpoint_monitoring_base.py +8 -11
- reconcile/cluster_deployment_mapper.py +2 -5
- reconcile/cna/assets/asset.py +4 -7
- reconcile/cna/assets/null.py +2 -5
- reconcile/cna/integration.py +2 -3
- reconcile/cna/state.py +2 -5
- reconcile/dashdotdb_base.py +8 -11
- reconcile/dashdotdb_cso.py +3 -6
- reconcile/dashdotdb_dora.py +10 -14
- reconcile/dashdotdb_dvo.py +10 -13
- reconcile/dashdotdb_slo.py +5 -8
- reconcile/database_access_manager.py +5 -6
- reconcile/dynatrace_token_provider/integration.py +3 -6
- reconcile/dynatrace_token_provider/integration_v2.py +20 -0
- reconcile/dynatrace_token_provider/meta.py +1 -0
- reconcile/external_resources/integration.py +1 -1
- reconcile/external_resources/manager.py +4 -4
- reconcile/external_resources/model.py +3 -3
- reconcile/external_resources/secrets_sync.py +5 -5
- reconcile/external_resources/state.py +5 -5
- reconcile/gabi_authorized_users.py +3 -6
- reconcile/gcr_mirror.py +1 -1
- reconcile/github_org.py +1 -3
- reconcile/github_repo_invites.py +2 -5
- reconcile/gitlab_housekeeping.py +7 -11
- reconcile/gitlab_labeler.py +1 -2
- reconcile/gitlab_members.py +2 -5
- reconcile/gitlab_permissions.py +1 -3
- reconcile/glitchtip/integration.py +5 -8
- reconcile/glitchtip_project_alerts/integration.py +57 -33
- reconcile/glitchtip_project_dsn/integration.py +8 -11
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +6 -0
- reconcile/gql_definitions/fragments/aws_account_managed.py +8 -0
- reconcile/gql_definitions/glitchtip/glitchtip_project.py +4 -4
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +27 -7
- reconcile/integrations_manager.py +5 -8
- reconcile/jenkins/types.py +5 -6
- reconcile/jenkins_job_builder.py +9 -12
- reconcile/jenkins_roles.py +1 -1
- reconcile/jira_watcher.py +2 -2
- reconcile/ldap_groups/integration.py +2 -5
- reconcile/ocm/types.py +21 -26
- reconcile/ocm_addons_upgrade_tests_trigger.py +3 -6
- reconcile/ocm_clusters.py +8 -8
- reconcile/ocm_internal_notifications/integration.py +1 -2
- reconcile/ocm_labels/integration.py +2 -5
- reconcile/ocm_machine_pools.py +11 -15
- reconcile/ocm_upgrade_scheduler_org_updater.py +2 -5
- reconcile/openshift_base.py +29 -30
- reconcile/openshift_groups.py +15 -20
- reconcile/openshift_namespace_labels.py +8 -14
- reconcile/openshift_namespaces.py +5 -8
- reconcile/openshift_network_policies.py +2 -4
- reconcile/openshift_resources_base.py +19 -29
- reconcile/openshift_saas_deploy.py +9 -10
- reconcile/openshift_saas_deploy_change_tester.py +7 -10
- reconcile/openshift_saas_deploy_trigger_base.py +4 -7
- reconcile/openshift_saas_deploy_trigger_cleaner.py +5 -8
- reconcile/openshift_saas_deploy_trigger_configs.py +1 -2
- reconcile/openshift_saas_deploy_trigger_images.py +1 -2
- reconcile/openshift_saas_deploy_trigger_moving_commits.py +1 -2
- reconcile/openshift_saas_deploy_trigger_upstream_jobs.py +1 -2
- reconcile/openshift_tekton_resources.py +7 -11
- reconcile/openshift_upgrade_watcher.py +10 -13
- reconcile/openshift_users.py +8 -11
- reconcile/oum/base.py +3 -4
- reconcile/oum/labelset.py +1 -2
- reconcile/oum/metrics.py +2 -2
- reconcile/oum/models.py +1 -2
- reconcile/oum/standalone.py +2 -3
- reconcile/prometheus_rules_tester/integration.py +6 -9
- reconcile/quay_membership.py +1 -2
- reconcile/quay_mirror.py +12 -13
- reconcile/quay_mirror_org.py +10 -10
- reconcile/queries.py +4 -7
- reconcile/resource_scraper.py +3 -4
- reconcile/rhidp/common.py +2 -2
- reconcile/saas_auto_promotions_manager/integration.py +5 -6
- reconcile/saas_auto_promotions_manager/merge_request_manager/batcher.py +1 -2
- reconcile/saas_auto_promotions_manager/publisher.py +5 -6
- reconcile/saas_auto_promotions_manager/subscriber.py +36 -15
- reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py +8 -0
- reconcile/saas_file_validator.py +2 -5
- reconcile/signalfx_endpoint_monitoring.py +2 -5
- reconcile/skupper_network/integration.py +3 -6
- reconcile/skupper_network/models.py +3 -5
- reconcile/slack_base.py +4 -7
- reconcile/slack_usergroups.py +15 -17
- reconcile/sql_query.py +5 -9
- reconcile/status_board.py +4 -5
- reconcile/statuspage/atlassian.py +14 -15
- reconcile/statuspage/integrations/maintenances.py +3 -3
- reconcile/statuspage/page.py +8 -8
- reconcile/statuspage/state.py +4 -5
- reconcile/statuspage/status.py +7 -8
- reconcile/templating/lib/rendering.py +8 -8
- reconcile/templating/renderer.py +10 -11
- reconcile/templating/validator.py +4 -4
- reconcile/terraform_aws_route53.py +3 -6
- reconcile/terraform_cloudflare_dns.py +9 -12
- reconcile/terraform_cloudflare_resources.py +9 -11
- reconcile/terraform_cloudflare_users.py +8 -11
- reconcile/terraform_init/integration.py +2 -2
- reconcile/terraform_repo.py +11 -14
- reconcile/terraform_resources.py +20 -21
- reconcile/terraform_tgw_attachments.py +32 -36
- reconcile/terraform_users.py +6 -7
- reconcile/terraform_vpc_resources/integration.py +6 -6
- reconcile/test/conftest.py +7 -10
- reconcile/test/fixtures.py +1 -1
- reconcile/test/saas_auto_promotions_manager/conftest.py +3 -2
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +2 -2
- reconcile/test/test_database_access_manager.py +3 -6
- reconcile/test/test_gitlab_labeler.py +2 -5
- reconcile/test/test_jump_host.py +5 -8
- reconcile/test/test_ocm_machine_pools.py +1 -4
- reconcile/test/test_openshift_base.py +3 -6
- reconcile/test/test_openshift_cluster_bots.py +5 -5
- reconcile/test/test_openshift_namespace_labels.py +2 -3
- reconcile/test/test_openshift_saas_deploy_trigger_cleaner.py +2 -2
- reconcile/test/test_saasherder.py +9 -12
- reconcile/test/test_slack_base.py +4 -6
- reconcile/test/test_status_board.py +4 -7
- reconcile/test/test_terraform_tgw_attachments.py +14 -20
- reconcile/typed_queries/alerting_services_settings.py +1 -2
- reconcile/typed_queries/app_interface_custom_messages.py +2 -3
- reconcile/typed_queries/app_interface_deadmanssnitch_settings.py +1 -3
- reconcile/typed_queries/app_interface_repo_url.py +1 -2
- reconcile/typed_queries/app_interface_state_settings.py +1 -3
- reconcile/typed_queries/app_interface_vault_settings.py +1 -2
- reconcile/typed_queries/aws_vpc_requests.py +1 -3
- reconcile/typed_queries/aws_vpcs.py +1 -3
- reconcile/typed_queries/clusters.py +2 -4
- reconcile/typed_queries/clusters_minimal.py +1 -3
- reconcile/typed_queries/clusters_with_dms.py +1 -3
- reconcile/typed_queries/dynatrace_environments.py +14 -0
- reconcile/typed_queries/external_resources.py +3 -4
- reconcile/typed_queries/pagerduty_instances.py +1 -2
- reconcile/typed_queries/repos.py +2 -3
- reconcile/typed_queries/reserved_networks.py +1 -3
- reconcile/typed_queries/saas_files.py +49 -59
- reconcile/typed_queries/slo_documents.py +1 -3
- reconcile/typed_queries/status_board.py +3 -7
- reconcile/typed_queries/tekton_pipeline_providers.py +1 -2
- reconcile/typed_queries/terraform_namespaces.py +1 -2
- reconcile/typed_queries/terraform_tgw_attachments/aws_accounts.py +1 -3
- reconcile/utils/acs/base.py +2 -3
- reconcile/utils/acs/notifiers.py +3 -3
- reconcile/utils/acs/policies.py +3 -3
- reconcile/utils/aggregated_list.py +1 -1
- reconcile/utils/amtool.py +1 -2
- reconcile/utils/aws_api.py +28 -31
- reconcile/utils/aws_api_typed/account.py +23 -0
- reconcile/utils/aws_api_typed/api.py +20 -9
- reconcile/utils/binary.py +1 -3
- reconcile/utils/clusterhealth/providerbase.py +1 -2
- reconcile/utils/clusterhealth/telemeter.py +2 -2
- reconcile/utils/deadmanssnitch_api.py +1 -2
- reconcile/utils/disabled_integrations.py +4 -6
- reconcile/utils/environ.py +1 -1
- reconcile/utils/expiration.py +3 -7
- reconcile/utils/external_resource_spec.py +3 -4
- reconcile/utils/external_resources.py +4 -7
- reconcile/utils/filtering.py +1 -2
- reconcile/utils/git.py +3 -9
- reconcile/utils/git_secrets.py +5 -5
- reconcile/utils/github_api.py +5 -9
- reconcile/utils/gitlab_api.py +2 -3
- reconcile/utils/glitchtip/client.py +2 -4
- reconcile/utils/glitchtip/models.py +8 -11
- reconcile/utils/gql.py +26 -35
- reconcile/utils/grouping.py +1 -3
- reconcile/utils/imap_client.py +2 -5
- reconcile/utils/internal_groups/client.py +1 -2
- reconcile/utils/internal_groups/models.py +8 -9
- reconcile/utils/jenkins_api.py +4 -4
- reconcile/utils/jinja2/extensions.py +1 -1
- reconcile/utils/jinja2/filters.py +4 -4
- reconcile/utils/jinja2/utils.py +16 -16
- reconcile/utils/jira_client.py +10 -11
- reconcile/utils/jjb_client.py +14 -17
- reconcile/utils/jobcontroller/controller.py +5 -5
- reconcile/utils/jobcontroller/models.py +2 -2
- reconcile/utils/jsonpath.py +4 -5
- reconcile/utils/jump_host.py +7 -8
- reconcile/utils/keycloak.py +3 -7
- reconcile/utils/ldap_client.py +2 -3
- reconcile/utils/lean_terraform_client.py +13 -17
- reconcile/utils/membershipsources/app_interface_resolver.py +1 -1
- reconcile/utils/membershipsources/models.py +19 -22
- reconcile/utils/metrics.py +13 -15
- reconcile/utils/mr/base.py +7 -11
- reconcile/utils/mr/glitchtip_access_reporter.py +2 -2
- reconcile/utils/mr/notificator.py +1 -2
- reconcile/utils/oc.py +38 -38
- reconcile/utils/oc_connection_parameters.py +24 -25
- reconcile/utils/oc_filters.py +2 -3
- reconcile/utils/oc_map.py +9 -15
- reconcile/utils/ocm/addons.py +7 -10
- reconcile/utils/ocm/base.py +38 -39
- reconcile/utils/ocm/clusters.py +6 -9
- reconcile/utils/ocm/label_sources.py +1 -2
- reconcile/utils/ocm/labels.py +3 -6
- reconcile/utils/ocm/ocm.py +11 -14
- reconcile/utils/ocm/products.py +1 -3
- reconcile/utils/ocm/search_filters.py +16 -17
- reconcile/utils/ocm/service_log.py +2 -3
- reconcile/utils/ocm/sre_capability_labels.py +4 -8
- reconcile/utils/ocm/subscriptions.py +1 -3
- reconcile/utils/ocm/syncsets.py +2 -4
- reconcile/utils/ocm/upgrades.py +5 -9
- reconcile/utils/ocm_base_client.py +13 -16
- reconcile/utils/openshift_resource.py +5 -11
- reconcile/utils/output.py +2 -3
- reconcile/utils/pagerduty_api.py +4 -5
- reconcile/utils/prometheus.py +2 -2
- reconcile/utils/promotion_state.py +4 -5
- reconcile/utils/promtool.py +2 -8
- reconcile/utils/quay_api.py +12 -22
- reconcile/utils/raw_github_api.py +3 -5
- reconcile/utils/rosa/rosa_cli.py +6 -6
- reconcile/utils/rosa/session.py +6 -7
- reconcile/utils/runtime/desired_state_diff.py +3 -8
- reconcile/utils/runtime/environment.py +4 -7
- reconcile/utils/runtime/integration.py +4 -4
- reconcile/utils/runtime/meta.py +1 -2
- reconcile/utils/runtime/runner.py +7 -10
- reconcile/utils/runtime/sharding.py +22 -27
- reconcile/utils/saasherder/interfaces.py +63 -69
- reconcile/utils/saasherder/models.py +30 -35
- reconcile/utils/saasherder/saasherder.py +39 -54
- reconcile/utils/secret_reader.py +17 -19
- reconcile/utils/slack_api.py +15 -17
- reconcile/utils/smtp_client.py +1 -2
- reconcile/utils/sqs_gateway.py +1 -3
- reconcile/utils/state.py +1 -2
- reconcile/utils/terraform/config_client.py +4 -5
- reconcile/utils/terraform_client.py +12 -8
- reconcile/utils/terrascript/cloudflare_client.py +4 -10
- reconcile/utils/terrascript/cloudflare_resources.py +10 -13
- reconcile/utils/terrascript/models.py +2 -3
- reconcile/utils/terrascript/resources.py +1 -2
- reconcile/utils/terrascript_aws_client.py +50 -38
- reconcile/utils/unleash/client.py +4 -7
- reconcile/utils/unleash/server.py +2 -2
- reconcile/utils/vault.py +8 -11
- reconcile/utils/vaultsecretref.py +2 -3
- reconcile/utils/vcs.py +7 -8
- reconcile/vault_replication.py +4 -8
- reconcile/vpc_peerings_validator.py +4 -9
- release/version.py +6 -7
- tools/app_interface_reporter.py +2 -2
- tools/cli_commands/gpg_encrypt.py +3 -6
- tools/cli_commands/systems_and_tools.py +4 -7
- tools/qontract_cli.py +105 -17
- tools/saas_promotion_state/__init__.py +0 -0
- tools/saas_promotion_state/saas_promotion_state.py +105 -0
- tools/template_validation.py +1 -1
- tools/test/conftest.py +45 -6
- tools/test/test_saas_promotion_state.py +187 -0
- {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/top_level.txt +0 -0
tools/qontract_cli.py
CHANGED
@@ -9,16 +9,13 @@ import re
|
|
9
9
|
import sys
|
10
10
|
from collections import defaultdict
|
11
11
|
from datetime import (
|
12
|
+
UTC,
|
12
13
|
datetime,
|
13
14
|
timedelta,
|
14
|
-
timezone,
|
15
15
|
)
|
16
16
|
from operator import itemgetter
|
17
17
|
from statistics import median
|
18
|
-
from typing import
|
19
|
-
Any,
|
20
|
-
Optional,
|
21
|
-
)
|
18
|
+
from typing import Any
|
22
19
|
|
23
20
|
import boto3
|
24
21
|
import click
|
@@ -59,6 +56,14 @@ from reconcile.cli import (
|
|
59
56
|
config_file,
|
60
57
|
use_jump_host,
|
61
58
|
)
|
59
|
+
from reconcile.external_resources.manager import (
|
60
|
+
FLAG_RESOURCE_MANAGED_BY_ERV2,
|
61
|
+
setup_factories,
|
62
|
+
)
|
63
|
+
from reconcile.external_resources.model import (
|
64
|
+
ExternalResourcesInventory,
|
65
|
+
load_module_inventory,
|
66
|
+
)
|
62
67
|
from reconcile.gql_definitions.advanced_upgrade_service.aus_clusters import (
|
63
68
|
query as aus_clusters_query,
|
64
69
|
)
|
@@ -75,6 +80,11 @@ from reconcile.typed_queries.app_interface_vault_settings import (
|
|
75
80
|
get_app_interface_vault_settings,
|
76
81
|
)
|
77
82
|
from reconcile.typed_queries.clusters import get_clusters
|
83
|
+
from reconcile.typed_queries.external_resources import (
|
84
|
+
get_modules,
|
85
|
+
get_namespaces,
|
86
|
+
get_settings,
|
87
|
+
)
|
78
88
|
from reconcile.typed_queries.saas_files import get_saas_files
|
79
89
|
from reconcile.typed_queries.slo_documents import get_slo_documents
|
80
90
|
from reconcile.typed_queries.status_board import get_status_board
|
@@ -352,8 +362,8 @@ def get_upgrade_policies_data(
|
|
352
362
|
|
353
363
|
def soaking_str(
|
354
364
|
soaking: dict[str, Any],
|
355
|
-
upgrade_policy:
|
356
|
-
upgradeable_version:
|
365
|
+
upgrade_policy: AbstractUpgradePolicy | None,
|
366
|
+
upgradeable_version: str | None,
|
357
367
|
) -> str:
|
358
368
|
if upgrade_policy:
|
359
369
|
upgrade_version = upgrade_policy.version
|
@@ -764,9 +774,9 @@ def ocm_addon_upgrade_policies(ctx: click.core.Context) -> None:
|
|
764
774
|
@click.pass_context
|
765
775
|
def sd_app_sre_alert_report(
|
766
776
|
ctx: click.core.Context,
|
767
|
-
days:
|
768
|
-
from_timestamp:
|
769
|
-
to_timestamp:
|
777
|
+
days: int | None,
|
778
|
+
from_timestamp: int | None,
|
779
|
+
to_timestamp: int | None,
|
770
780
|
) -> None:
|
771
781
|
import tools.sd_app_sre_alert_report as report
|
772
782
|
|
@@ -1407,9 +1417,7 @@ def rosa_create_cluster_command(ctx, cluster_name):
|
|
1407
1417
|
@click.argument("jumphost_hostname", required=False)
|
1408
1418
|
@click.argument("cluster_name", required=False)
|
1409
1419
|
@click.pass_context
|
1410
|
-
def sshuttle_command(
|
1411
|
-
ctx, jumphost_hostname: Optional[str], cluster_name: Optional[str]
|
1412
|
-
):
|
1420
|
+
def sshuttle_command(ctx, jumphost_hostname: str | None, cluster_name: str | None):
|
1413
1421
|
jumphosts_query_data = queries.get_jumphosts(hostname=jumphost_hostname)
|
1414
1422
|
jumphosts = jumphosts_query_data.jumphosts or []
|
1415
1423
|
for jh in jumphosts:
|
@@ -2358,7 +2366,7 @@ def ec2_jenkins_workers(ctx, aws_access_key_id, aws_secret_access_key, aws_regio
|
|
2358
2366
|
client = boto3.client("autoscaling")
|
2359
2367
|
ec2 = boto3.resource("ec2")
|
2360
2368
|
results = []
|
2361
|
-
now = datetime.now(
|
2369
|
+
now = datetime.now(UTC)
|
2362
2370
|
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
2363
2371
|
columns = [
|
2364
2372
|
"type",
|
@@ -2511,7 +2519,7 @@ def alerts(ctx, file_path):
|
|
2511
2519
|
case _:
|
2512
2520
|
return BIG_NUMBER
|
2513
2521
|
|
2514
|
-
with open(file_path,
|
2522
|
+
with open(file_path, encoding="locale") as f:
|
2515
2523
|
content = json.loads(f.read())
|
2516
2524
|
|
2517
2525
|
columns = [
|
@@ -2605,7 +2613,7 @@ def osd_component_versions(ctx):
|
|
2605
2613
|
@get.command()
|
2606
2614
|
@click.pass_context
|
2607
2615
|
def maintenances(ctx):
|
2608
|
-
now = datetime.now(
|
2616
|
+
now = datetime.now(UTC)
|
2609
2617
|
maintenances = maintenances_gql.query(gql.get_api().query).maintenances or []
|
2610
2618
|
data = [
|
2611
2619
|
{
|
@@ -3474,7 +3482,7 @@ def saas_dev(ctx, app_name=None, saas_file_name=None, env_name=None) -> None:
|
|
3474
3482
|
@click.option("--app-name", default=None, help="app to act on.")
|
3475
3483
|
@click.pass_context
|
3476
3484
|
def saas_targets(
|
3477
|
-
ctx, saas_file_name:
|
3485
|
+
ctx, saas_file_name: str | None = None, app_name: str | None = None
|
3478
3486
|
) -> None:
|
3479
3487
|
"""Resolve namespaceSelectors and print all resulting targets of a saas file."""
|
3480
3488
|
console = Console()
|
@@ -3663,6 +3671,46 @@ def gpg_encrypt(
|
|
3663
3671
|
).execute()
|
3664
3672
|
|
3665
3673
|
|
3674
|
+
@root.command()
|
3675
|
+
@click.option("--channel", help="the channel that state is part of")
|
3676
|
+
@click.option("--sha", help="the commit sha we want state for")
|
3677
|
+
@environ(["APP_INTERFACE_STATE_BUCKET"])
|
3678
|
+
def get_promotion_state(channel: str, sha: str):
|
3679
|
+
from tools.saas_promotion_state.saas_promotion_state import (
|
3680
|
+
SaasPromotionState,
|
3681
|
+
)
|
3682
|
+
|
3683
|
+
promotion_state = SaasPromotionState.create(promotion_state=None, saas_files=None)
|
3684
|
+
for publisher_id, state in promotion_state.get(channel=channel, sha=sha).items():
|
3685
|
+
print()
|
3686
|
+
if not state:
|
3687
|
+
print(f"No state found for {publisher_id=}")
|
3688
|
+
else:
|
3689
|
+
print(f"State for {publisher_id=}:")
|
3690
|
+
print(state)
|
3691
|
+
|
3692
|
+
|
3693
|
+
@root.command()
|
3694
|
+
@click.option("--channel", help="the channel that state is part of")
|
3695
|
+
@click.option("--sha", help="the commit sha we want state for")
|
3696
|
+
@click.option("--publisher-id", help="the publisher id we want state for")
|
3697
|
+
@environ(["APP_INTERFACE_STATE_BUCKET"])
|
3698
|
+
def mark_promotion_state_successful(channel: str, sha: str, publisher_id: str):
|
3699
|
+
from tools.saas_promotion_state.saas_promotion_state import (
|
3700
|
+
SaasPromotionState,
|
3701
|
+
)
|
3702
|
+
|
3703
|
+
promotion_state = SaasPromotionState.create(promotion_state=None, saas_files=None)
|
3704
|
+
print(f"Current states for {publisher_id=}")
|
3705
|
+
print(promotion_state.get(channel=channel, sha=sha).get(publisher_id, None))
|
3706
|
+
print()
|
3707
|
+
print("Pushing new state ...")
|
3708
|
+
promotion_state.set_successful(channel=channel, sha=sha, publisher_uid=publisher_id)
|
3709
|
+
print()
|
3710
|
+
print(f"New state for {publisher_id=}")
|
3711
|
+
print(promotion_state.get(channel=channel, sha=sha).get(publisher_id, None))
|
3712
|
+
|
3713
|
+
|
3666
3714
|
@root.command()
|
3667
3715
|
@click.option("--change-type-name")
|
3668
3716
|
@click.option("--role-name")
|
@@ -3770,5 +3818,45 @@ def remove(ctx, sso_client_vault_secret_path: str):
|
|
3770
3818
|
)
|
3771
3819
|
|
3772
3820
|
|
3821
|
+
@root.group()
|
3822
|
+
@click.pass_context
|
3823
|
+
def external_resources(ctx):
|
3824
|
+
"""External resources commands"""
|
3825
|
+
|
3826
|
+
|
3827
|
+
@external_resources.command()
|
3828
|
+
@click.argument("provision-provider", required=True)
|
3829
|
+
@click.argument("provisioner", required=True)
|
3830
|
+
@click.argument("provider", required=True)
|
3831
|
+
@click.argument("identifier", required=True)
|
3832
|
+
@click.pass_context
|
3833
|
+
def get_input(
|
3834
|
+
ctx, provision_provider: str, provisioner: str, provider: str, identifier: str
|
3835
|
+
):
|
3836
|
+
"""Gets the input data for an external resource asset. Input data is what is used
|
3837
|
+
in the Reconciliation Job to manage the resource.
|
3838
|
+
|
3839
|
+
e.g: qontract-reconcile --config=<config> external-resources get-input aws app-sre-stage rds dashdotdb-stage
|
3840
|
+
"""
|
3841
|
+
namespaces = [ns for ns in get_namespaces() if ns.external_resources]
|
3842
|
+
er_inventory = ExternalResourcesInventory(namespaces)
|
3843
|
+
|
3844
|
+
spec = er_inventory.get_inventory_spec(
|
3845
|
+
provision_provider=provision_provider,
|
3846
|
+
provisioner=provisioner,
|
3847
|
+
provider=provider,
|
3848
|
+
identifier=identifier,
|
3849
|
+
)
|
3850
|
+
vault_settings = get_app_interface_vault_settings()
|
3851
|
+
secret_reader = create_secret_reader(use_vault=vault_settings.vault)
|
3852
|
+
er_settings = get_settings()[0]
|
3853
|
+
m_inventory = load_module_inventory(get_modules())
|
3854
|
+
factories = setup_factories(er_settings, m_inventory, er_inventory, secret_reader)
|
3855
|
+
f = factories.get_factory(spec.provision_provider)
|
3856
|
+
resource = f.create_external_resource(spec)
|
3857
|
+
f.validate_external_resource(resource)
|
3858
|
+
print(resource.json(exclude={"data": {FLAG_RESOURCE_MANAGED_BY_ERV2}}))
|
3859
|
+
|
3860
|
+
|
3773
3861
|
if __name__ == "__main__":
|
3774
3862
|
root() # pylint: disable=no-value-for-parameter
|
File without changes
|
@@ -0,0 +1,105 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from collections.abc import Iterable
|
4
|
+
|
5
|
+
from reconcile.openshift_saas_deploy import (
|
6
|
+
QONTRACT_INTEGRATION as OPENSHIFT_SAAS_DEPLOY,
|
7
|
+
)
|
8
|
+
from reconcile.typed_queries.app_interface_vault_settings import (
|
9
|
+
get_app_interface_vault_settings,
|
10
|
+
)
|
11
|
+
from reconcile.typed_queries.saas_files import SaasFile, get_saas_files
|
12
|
+
from reconcile.utils.promotion_state import PromotionData, PromotionState
|
13
|
+
from reconcile.utils.secret_reader import create_secret_reader
|
14
|
+
from reconcile.utils.state import init_state
|
15
|
+
|
16
|
+
|
17
|
+
class SaasPromotionStateException(Exception):
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
21
|
+
class SaasPromotionStateMissingException(Exception):
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
class SaasPromotionState:
|
26
|
+
def __init__(
|
27
|
+
self, promotion_state: PromotionState, saas_files: Iterable[SaasFile]
|
28
|
+
) -> None:
|
29
|
+
self._promotion_state = promotion_state
|
30
|
+
self._saas_files = saas_files
|
31
|
+
|
32
|
+
def _publisher_ids_for_channel(
|
33
|
+
self, channel: str, saas_files: Iterable[SaasFile]
|
34
|
+
) -> list[str]:
|
35
|
+
publisher_uids: list[str] = []
|
36
|
+
for saas_file in saas_files:
|
37
|
+
for resource_template in saas_file.resource_templates:
|
38
|
+
for target in resource_template.targets:
|
39
|
+
if not target.promotion:
|
40
|
+
continue
|
41
|
+
for publish_channel in target.promotion.publish or []:
|
42
|
+
if publish_channel == channel:
|
43
|
+
publisher_uids.append(
|
44
|
+
target.uid(
|
45
|
+
parent_saas_file_name=saas_file.name,
|
46
|
+
parent_resource_template_name=resource_template.name,
|
47
|
+
)
|
48
|
+
)
|
49
|
+
return publisher_uids
|
50
|
+
|
51
|
+
def get(self, channel: str, sha: str) -> dict[str, PromotionData | None]:
|
52
|
+
return {
|
53
|
+
publisher_id: self._promotion_state.get_promotion_data(
|
54
|
+
sha=sha,
|
55
|
+
channel=channel,
|
56
|
+
use_cache=False,
|
57
|
+
target_uid=publisher_id,
|
58
|
+
pre_check_sha_exists=False,
|
59
|
+
)
|
60
|
+
for publisher_id in self._publisher_ids_for_channel(
|
61
|
+
channel=channel, saas_files=self._saas_files
|
62
|
+
)
|
63
|
+
}
|
64
|
+
|
65
|
+
def set_successful(self, channel: str, sha: str, publisher_uid: str) -> None:
|
66
|
+
current_data = self._promotion_state.get_promotion_data(
|
67
|
+
sha=sha,
|
68
|
+
channel=channel,
|
69
|
+
target_uid=publisher_uid,
|
70
|
+
use_cache=False,
|
71
|
+
pre_check_sha_exists=False,
|
72
|
+
)
|
73
|
+
|
74
|
+
if not current_data:
|
75
|
+
raise SaasPromotionStateMissingException(
|
76
|
+
f"No promotion state in S3 for given {publisher_uid=} {sha=} {channel=}"
|
77
|
+
)
|
78
|
+
|
79
|
+
if current_data.success:
|
80
|
+
raise SaasPromotionStateException(
|
81
|
+
f"The current promotion state is already marked successful for given {publisher_uid=} {sha=} {channel=}",
|
82
|
+
current_data,
|
83
|
+
)
|
84
|
+
|
85
|
+
current_data.success = True
|
86
|
+
self._promotion_state.publish_promotion_data(
|
87
|
+
data=current_data, sha=sha, channel=channel, target_uid=publisher_uid
|
88
|
+
)
|
89
|
+
|
90
|
+
@staticmethod
|
91
|
+
def create(
|
92
|
+
promotion_state: PromotionState | None, saas_files: Iterable[SaasFile] | None
|
93
|
+
) -> SaasPromotionState:
|
94
|
+
if not promotion_state:
|
95
|
+
vault_settings = get_app_interface_vault_settings()
|
96
|
+
secret_reader = create_secret_reader(use_vault=vault_settings.vault)
|
97
|
+
saas_deploy_state = init_state(
|
98
|
+
integration=OPENSHIFT_SAAS_DEPLOY, secret_reader=secret_reader
|
99
|
+
)
|
100
|
+
promotion_state = PromotionState(state=saas_deploy_state)
|
101
|
+
if not saas_files:
|
102
|
+
saas_files = get_saas_files()
|
103
|
+
return SaasPromotionState(
|
104
|
+
promotion_state=promotion_state, saas_files=saas_files
|
105
|
+
)
|
tools/template_validation.py
CHANGED
@@ -27,7 +27,7 @@ def load_clean_yaml(path: str) -> dict:
|
|
27
27
|
|
28
28
|
def load_yaml(to_load: str) -> dict:
|
29
29
|
ruamel_instance = create_ruamel_instance()
|
30
|
-
with open(to_load,
|
30
|
+
with open(to_load, encoding="utf-8") as file:
|
31
31
|
return ruamel_instance.load(file)
|
32
32
|
|
33
33
|
|
tools/test/conftest.py
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
from collections.abc import (
|
2
2
|
Callable,
|
3
|
+
Iterable,
|
4
|
+
Mapping,
|
3
5
|
MutableMapping,
|
4
6
|
)
|
5
|
-
from
|
6
|
-
|
7
|
-
Optional,
|
8
|
-
)
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Any
|
9
9
|
|
10
10
|
import pytest
|
11
11
|
from pydantic import BaseModel
|
12
12
|
from pydantic.error_wrappers import ValidationError
|
13
13
|
|
14
|
+
from reconcile.typed_queries.saas_files import SaasFile
|
14
15
|
from reconcile.utils.models import data_default_none
|
15
16
|
|
16
17
|
|
@@ -18,17 +19,55 @@ class GQLClassFactoryError(Exception):
|
|
18
19
|
pass
|
19
20
|
|
20
21
|
|
22
|
+
@pytest.fixture
|
23
|
+
def saas_files_builder(
|
24
|
+
gql_class_factory: Callable[[type[SaasFile], Mapping], SaasFile],
|
25
|
+
) -> Callable[[Iterable[MutableMapping]], list[SaasFile]]:
|
26
|
+
def builder(data: Iterable[MutableMapping]) -> list[SaasFile]:
|
27
|
+
for d in data:
|
28
|
+
if "app" not in d:
|
29
|
+
d["app"] = {}
|
30
|
+
if "pipelinesProvider" not in d:
|
31
|
+
d["pipelinesProvider"] = {}
|
32
|
+
if "managedResourceTypes" not in d:
|
33
|
+
d["managedResourceTypes"] = []
|
34
|
+
if "imagePatterns" not in d:
|
35
|
+
d["imagePatterns"] = []
|
36
|
+
for rt in d.get("resourceTemplates", []):
|
37
|
+
for t in rt.get("targets", []):
|
38
|
+
ns = t["namespace"]
|
39
|
+
if "name" not in ns:
|
40
|
+
ns["name"] = "some_name"
|
41
|
+
if "environment" not in ns:
|
42
|
+
ns["environment"] = {}
|
43
|
+
if "app" not in ns:
|
44
|
+
ns["app"] = {}
|
45
|
+
if "cluster" not in ns:
|
46
|
+
ns["cluster"] = {}
|
47
|
+
return [gql_class_factory(SaasFile, d) for d in data]
|
48
|
+
|
49
|
+
return builder
|
50
|
+
|
51
|
+
|
52
|
+
@pytest.fixture
|
53
|
+
def fx() -> Callable:
|
54
|
+
def _fx(name: str) -> str:
|
55
|
+
return (Path(__file__).parent / "fixtures" / name).read_text()
|
56
|
+
|
57
|
+
return _fx
|
58
|
+
|
59
|
+
|
21
60
|
@pytest.fixture
|
22
61
|
def gql_class_factory() -> (
|
23
62
|
Callable[
|
24
|
-
[type[BaseModel],
|
63
|
+
[type[BaseModel], MutableMapping[str, Any] | None],
|
25
64
|
BaseModel,
|
26
65
|
]
|
27
66
|
):
|
28
67
|
"""Create a GQL class from a fixture and set default values to None."""
|
29
68
|
|
30
69
|
def _gql_class_factory(
|
31
|
-
klass: type[BaseModel], data:
|
70
|
+
klass: type[BaseModel], data: MutableMapping[str, Any] | None = None
|
32
71
|
) -> BaseModel:
|
33
72
|
try:
|
34
73
|
return klass(**data_default_none(klass, data or {}))
|
@@ -0,0 +1,187 @@
|
|
1
|
+
from collections.abc import (
|
2
|
+
Callable,
|
3
|
+
Iterable,
|
4
|
+
Mapping,
|
5
|
+
)
|
6
|
+
from unittest.mock import (
|
7
|
+
create_autospec,
|
8
|
+
)
|
9
|
+
|
10
|
+
from pytest import raises
|
11
|
+
|
12
|
+
from reconcile.typed_queries.saas_files import SaasFile
|
13
|
+
from reconcile.utils.promotion_state import PromotionData, PromotionState
|
14
|
+
from tools.saas_promotion_state.saas_promotion_state import (
|
15
|
+
SaasPromotionState,
|
16
|
+
SaasPromotionStateException,
|
17
|
+
SaasPromotionStateMissingException,
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
def test_get_saas_promotion_state(
|
22
|
+
saas_files_builder: Callable[[Iterable[Mapping]], list[SaasFile]],
|
23
|
+
) -> None:
|
24
|
+
saas_files = saas_files_builder([
|
25
|
+
{
|
26
|
+
"path": "/saas1.yml",
|
27
|
+
"name": "saas_1",
|
28
|
+
"resourceTemplates": [
|
29
|
+
{
|
30
|
+
"name": "template_1",
|
31
|
+
"url": "repo1/url",
|
32
|
+
"targets": [
|
33
|
+
{
|
34
|
+
"ref": "main",
|
35
|
+
"namespace": {"path": "/namespace1.yml"},
|
36
|
+
"promotion": {
|
37
|
+
"publish": ["channel-a"],
|
38
|
+
},
|
39
|
+
}
|
40
|
+
],
|
41
|
+
}
|
42
|
+
],
|
43
|
+
},
|
44
|
+
{
|
45
|
+
"path": "/saas2.yml",
|
46
|
+
"name": "saas_2",
|
47
|
+
"resourceTemplates": [
|
48
|
+
{
|
49
|
+
"name": "template_2",
|
50
|
+
"url": "repo2/url",
|
51
|
+
"targets": [
|
52
|
+
{
|
53
|
+
"ref": "main",
|
54
|
+
"namespace": {"path": "/namespace2.yml"},
|
55
|
+
"promotion": {
|
56
|
+
"publish": ["channel-b"],
|
57
|
+
"subscribe": ["channel-a"],
|
58
|
+
},
|
59
|
+
},
|
60
|
+
{
|
61
|
+
"ref": "main",
|
62
|
+
"namespace": {"path": "/namespace3.yml"},
|
63
|
+
},
|
64
|
+
],
|
65
|
+
}
|
66
|
+
],
|
67
|
+
},
|
68
|
+
])
|
69
|
+
|
70
|
+
expected = PromotionData(
|
71
|
+
check_in="test1",
|
72
|
+
saas_file="test2",
|
73
|
+
success=True,
|
74
|
+
target_config_hash="test3",
|
75
|
+
)
|
76
|
+
promotion_state = create_autospec(spec=PromotionState)
|
77
|
+
promotion_state.get_promotion_data.return_value = expected
|
78
|
+
saas_promotion_state = SaasPromotionState.create(
|
79
|
+
promotion_state=promotion_state, saas_files=saas_files
|
80
|
+
)
|
81
|
+
result = saas_promotion_state.get(channel="channel-a", sha="main")
|
82
|
+
|
83
|
+
assert result == {"616af45d7fad7f4eea8d52b8b5e8a058cef82ab0": expected}
|
84
|
+
promotion_state.get_promotion_data.assert_called_once_with(
|
85
|
+
sha="main",
|
86
|
+
channel="channel-a",
|
87
|
+
use_cache=False,
|
88
|
+
target_uid="616af45d7fad7f4eea8d52b8b5e8a058cef82ab0",
|
89
|
+
pre_check_sha_exists=False,
|
90
|
+
)
|
91
|
+
|
92
|
+
|
93
|
+
def test_set_saas_promotion_state_success(
|
94
|
+
saas_files_builder: Callable[[Iterable[Mapping]], list[SaasFile]],
|
95
|
+
) -> None:
|
96
|
+
saas_files = saas_files_builder([{"resourceTemplates": []}])
|
97
|
+
|
98
|
+
current_data = PromotionData(
|
99
|
+
check_in="test1",
|
100
|
+
saas_file="test2",
|
101
|
+
success=False,
|
102
|
+
target_config_hash="test3",
|
103
|
+
)
|
104
|
+
promotion_state = create_autospec(spec=PromotionState)
|
105
|
+
promotion_state.get_promotion_data.return_value = current_data
|
106
|
+
saas_promotion_state = SaasPromotionState.create(
|
107
|
+
promotion_state=promotion_state, saas_files=saas_files
|
108
|
+
)
|
109
|
+
saas_promotion_state.set_successful(
|
110
|
+
channel="test-channel", sha="test-sha", publisher_uid="test-uid"
|
111
|
+
)
|
112
|
+
|
113
|
+
promotion_state.get_promotion_data.assert_called_once_with(
|
114
|
+
sha="test-sha",
|
115
|
+
channel="test-channel",
|
116
|
+
use_cache=False,
|
117
|
+
target_uid="test-uid",
|
118
|
+
pre_check_sha_exists=False,
|
119
|
+
)
|
120
|
+
promotion_state.publish_promotion_data.assert_called_once_with(
|
121
|
+
data=PromotionData(
|
122
|
+
check_in="test1",
|
123
|
+
saas_file="test2",
|
124
|
+
success=True,
|
125
|
+
target_config_hash="test3",
|
126
|
+
),
|
127
|
+
channel="test-channel",
|
128
|
+
sha="test-sha",
|
129
|
+
target_uid="test-uid",
|
130
|
+
)
|
131
|
+
|
132
|
+
|
133
|
+
def test_set_saas_promotion_state_missing(
|
134
|
+
saas_files_builder: Callable[[Iterable[Mapping]], list[SaasFile]],
|
135
|
+
) -> None:
|
136
|
+
saas_files = saas_files_builder([{"resourceTemplates": []}])
|
137
|
+
promotion_state = create_autospec(spec=PromotionState)
|
138
|
+
promotion_state.get_promotion_data.return_value = None
|
139
|
+
saas_promotion_state = SaasPromotionState.create(
|
140
|
+
promotion_state=promotion_state, saas_files=saas_files
|
141
|
+
)
|
142
|
+
|
143
|
+
with raises(SaasPromotionStateMissingException):
|
144
|
+
saas_promotion_state.set_successful(
|
145
|
+
channel="test-channel", sha="test-sha", publisher_uid="test-uid"
|
146
|
+
)
|
147
|
+
|
148
|
+
promotion_state.get_promotion_data.assert_called_once_with(
|
149
|
+
sha="test-sha",
|
150
|
+
channel="test-channel",
|
151
|
+
use_cache=False,
|
152
|
+
target_uid="test-uid",
|
153
|
+
pre_check_sha_exists=False,
|
154
|
+
)
|
155
|
+
promotion_state.publish_promotion_data.assert_not_called()
|
156
|
+
|
157
|
+
|
158
|
+
def test_set_saas_promotion_state_already_successful(
|
159
|
+
saas_files_builder: Callable[[Iterable[Mapping]], list[SaasFile]],
|
160
|
+
) -> None:
|
161
|
+
saas_files = saas_files_builder([{"resourceTemplates": []}])
|
162
|
+
|
163
|
+
current_data = PromotionData(
|
164
|
+
check_in="test1",
|
165
|
+
saas_file="test2",
|
166
|
+
success=True,
|
167
|
+
target_config_hash="test3",
|
168
|
+
)
|
169
|
+
promotion_state = create_autospec(spec=PromotionState)
|
170
|
+
promotion_state.get_promotion_data.return_value = current_data
|
171
|
+
saas_promotion_state = SaasPromotionState.create(
|
172
|
+
promotion_state=promotion_state, saas_files=saas_files
|
173
|
+
)
|
174
|
+
|
175
|
+
with raises(SaasPromotionStateException):
|
176
|
+
saas_promotion_state.set_successful(
|
177
|
+
channel="test-channel", sha="test-sha", publisher_uid="test-uid"
|
178
|
+
)
|
179
|
+
|
180
|
+
promotion_state.get_promotion_data.assert_called_once_with(
|
181
|
+
sha="test-sha",
|
182
|
+
channel="test-channel",
|
183
|
+
use_cache=False,
|
184
|
+
target_uid="test-uid",
|
185
|
+
pre_check_sha_exists=False,
|
186
|
+
)
|
187
|
+
promotion_state.publish_promotion_data.assert_not_called()
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc879.dist-info → qontract_reconcile-0.10.1rc894.dist-info}/top_level.txt
RENAMED
File without changes
|