qontract-reconcile 0.10.2.dev299__py3-none-any.whl → 0.10.2.dev430__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.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/METADATA +13 -12
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/RECORD +399 -394
- reconcile/acs_rbac.py +2 -2
- reconcile/aus/advanced_upgrade_service.py +18 -12
- reconcile/aus/base.py +134 -32
- reconcile/aus/cluster_version_data.py +15 -5
- reconcile/aus/models.py +3 -1
- reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
- reconcile/aus/ocm_upgrade_scheduler.py +8 -1
- reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
- reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
- reconcile/automated_actions/config/integration.py +16 -4
- reconcile/aws_account_manager/integration.py +8 -8
- reconcile/aws_account_manager/reconciler.py +3 -3
- reconcile/aws_ami_cleanup/integration.py +8 -12
- reconcile/aws_ami_share.py +69 -62
- reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
- reconcile/aws_ecr_image_pull_secrets.py +4 -4
- reconcile/aws_iam_keys.py +1 -0
- reconcile/aws_saml_idp/integration.py +12 -4
- reconcile/aws_saml_roles/integration.py +32 -25
- reconcile/aws_version_sync/integration.py +125 -84
- reconcile/change_owners/bundle.py +3 -3
- reconcile/change_owners/change_log_tracking.py +3 -2
- reconcile/change_owners/change_owners.py +1 -1
- reconcile/change_owners/diff.py +2 -4
- reconcile/checkpoint.py +12 -4
- reconcile/cli.py +111 -18
- reconcile/cluster_deployment_mapper.py +2 -3
- reconcile/dashdotdb_dora.py +5 -12
- reconcile/dashdotdb_slo.py +1 -1
- reconcile/database_access_manager.py +125 -121
- reconcile/deadmanssnitch.py +1 -5
- reconcile/dynatrace_token_provider/integration.py +1 -1
- reconcile/endpoints_discovery/integration.py +4 -1
- reconcile/endpoints_discovery/merge_request.py +1 -1
- reconcile/endpoints_discovery/merge_request_manager.py +9 -11
- reconcile/external_resources/factories.py +5 -12
- reconcile/external_resources/integration.py +1 -1
- reconcile/external_resources/manager.py +8 -5
- reconcile/external_resources/meta.py +0 -1
- reconcile/external_resources/metrics.py +1 -1
- reconcile/external_resources/model.py +20 -20
- reconcile/external_resources/reconciler.py +7 -4
- reconcile/external_resources/secrets_sync.py +8 -11
- reconcile/external_resources/state.py +26 -16
- reconcile/fleet_labeler/integration.py +1 -1
- reconcile/gabi_authorized_users.py +8 -5
- reconcile/gcp_image_mirror.py +2 -2
- reconcile/github_org.py +1 -1
- reconcile/github_owners.py +4 -0
- reconcile/gitlab_housekeeping.py +13 -15
- reconcile/gitlab_members.py +6 -12
- reconcile/gitlab_mr_sqs_consumer.py +2 -2
- reconcile/gitlab_owners.py +15 -11
- reconcile/gitlab_permissions.py +8 -12
- reconcile/glitchtip_project_alerts/integration.py +3 -1
- reconcile/gql_definitions/acs/acs_instances.py +10 -10
- reconcile/gql_definitions/acs/acs_policies.py +5 -5
- reconcile/gql_definitions/acs/acs_rbac.py +6 -6
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
- reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
- reconcile/gql_definitions/automated_actions/instance.py +51 -12
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
- reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
- reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
- reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
- reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
- reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
- reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
- reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
- reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
- reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_roles.py +120 -0
- reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
- reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
- reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
- reconcile/gql_definitions/common/apps.py +5 -5
- reconcile/gql_definitions/common/aws_vpc_requests.py +23 -10
- reconcile/gql_definitions/common/aws_vpcs.py +11 -11
- reconcile/gql_definitions/common/clusters.py +37 -35
- reconcile/gql_definitions/common/clusters_minimal.py +14 -14
- reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
- reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
- reconcile/gql_definitions/common/github_orgs.py +10 -10
- reconcile/gql_definitions/common/jira_settings.py +10 -10
- reconcile/gql_definitions/common/jiralert_settings.py +5 -5
- reconcile/gql_definitions/common/ldap_settings.py +5 -5
- reconcile/gql_definitions/common/namespaces.py +42 -44
- reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
- reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
- reconcile/gql_definitions/common/ocm_environments.py +19 -19
- reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
- reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
- reconcile/gql_definitions/common/pipeline_providers.py +29 -29
- reconcile/gql_definitions/common/quay_instances.py +5 -5
- reconcile/gql_definitions/common/quay_orgs.py +5 -5
- reconcile/gql_definitions/common/reserved_networks.py +5 -5
- reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
- reconcile/gql_definitions/common/saas_files.py +44 -44
- reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
- reconcile/gql_definitions/common/saasherder_settings.py +5 -5
- reconcile/gql_definitions/common/slack_workspaces.py +5 -5
- reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
- reconcile/gql_definitions/common/state_aws_account.py +7 -8
- reconcile/gql_definitions/common/users.py +5 -5
- reconcile/gql_definitions/common/users_with_paths.py +5 -5
- reconcile/gql_definitions/cost_report/app_names.py +5 -5
- reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
- reconcile/gql_definitions/cost_report/settings.py +9 -9
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
- reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
- reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
- reconcile/gql_definitions/email_sender/apps.py +5 -5
- reconcile/gql_definitions/email_sender/emails.py +8 -8
- reconcile/gql_definitions/email_sender/users.py +6 -6
- reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
- reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
- reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +492 -410
- reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
- reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
- reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
- reconcile/gql_definitions/fragments/aus_organization.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
- reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
- reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
- reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
- reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
- reconcile/gql_definitions/fragments/aws_vpc_request.py +10 -5
- reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
- reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
- reconcile/gql_definitions/fragments/disable.py +5 -5
- reconcile/gql_definitions/fragments/email_service.py +5 -5
- reconcile/gql_definitions/fragments/email_user.py +5 -5
- reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
- reconcile/gql_definitions/fragments/membership_source.py +5 -5
- reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
- reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
- reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
- reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
- reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
- reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_values.py +5 -5
- reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
- reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
- reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
- reconcile/gql_definitions/fragments/terraform_state.py +5 -5
- reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
- reconcile/gql_definitions/fragments/user.py +5 -5
- reconcile/gql_definitions/fragments/vault_secret.py +5 -5
- reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
- reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
- reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
- reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
- reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
- reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
- reconcile/gql_definitions/integrations/integrations.py +48 -51
- reconcile/gql_definitions/introspection.json +3050 -1393
- reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
- reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
- reconcile/gql_definitions/jira/jira_servers.py +5 -5
- reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
- reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
- reconcile/gql_definitions/ldap_groups/roles.py +5 -5
- reconcile/gql_definitions/ldap_groups/settings.py +9 -9
- reconcile/gql_definitions/maintenance/maintenances.py +5 -5
- reconcile/gql_definitions/membershipsources/roles.py +5 -5
- reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
- reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
- reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
- reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
- reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
- reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
- reconcile/gql_definitions/rhcs/certs.py +33 -87
- reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
- reconcile/gql_definitions/rhidp/organizations.py +18 -18
- reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
- reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
- reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
- reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
- reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
- reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
- reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
- reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
- reconcile/gql_definitions/slack_usergroups/users.py +5 -5
- reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
- reconcile/gql_definitions/status_board/status_board.py +6 -7
- reconcile/gql_definitions/statuspage/statuspages.py +9 -9
- reconcile/gql_definitions/templating/template_collection.py +5 -5
- reconcile/gql_definitions/templating/templates.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
- reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
- reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
- reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
- reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
- reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
- reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +448 -402
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
- reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
- reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
- reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
- reconcile/integrations_manager.py +3 -3
- reconcile/jenkins_job_builder.py +1 -1
- reconcile/jenkins_worker_fleets.py +80 -11
- reconcile/jira_permissions_validator.py +237 -122
- reconcile/ldap_groups/integration.py +1 -1
- reconcile/ocm/types.py +35 -56
- reconcile/ocm_aws_infrastructure_access.py +1 -1
- reconcile/ocm_clusters.py +4 -4
- reconcile/ocm_labels/integration.py +3 -2
- reconcile/ocm_machine_pools.py +33 -27
- reconcile/openshift_base.py +122 -10
- reconcile/openshift_cluster_bots.py +5 -5
- reconcile/openshift_groups.py +5 -0
- reconcile/openshift_limitranges.py +1 -1
- reconcile/openshift_namespace_labels.py +1 -1
- reconcile/openshift_namespaces.py +97 -101
- reconcile/openshift_resources_base.py +10 -5
- reconcile/openshift_rhcs_certs.py +77 -40
- reconcile/openshift_rolebindings.py +230 -130
- reconcile/openshift_saas_deploy.py +6 -7
- reconcile/openshift_saas_deploy_change_tester.py +9 -7
- reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
- reconcile/openshift_serviceaccount_tokens.py +8 -7
- reconcile/openshift_tekton_resources.py +1 -1
- reconcile/openshift_upgrade_watcher.py +4 -4
- reconcile/openshift_users.py +5 -3
- reconcile/oum/labelset.py +5 -3
- reconcile/oum/models.py +1 -4
- reconcile/oum/providers.py +1 -1
- reconcile/prometheus_rules_tester/integration.py +4 -4
- reconcile/quay_mirror.py +1 -1
- reconcile/queries.py +131 -0
- reconcile/requests_sender.py +8 -3
- reconcile/resource_scraper.py +1 -5
- reconcile/rhidp/common.py +5 -5
- reconcile/rhidp/sso_client/base.py +19 -10
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
- reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
- reconcile/sendgrid_teammates.py +20 -9
- reconcile/skupper_network/integration.py +2 -2
- reconcile/slack_usergroups.py +35 -14
- reconcile/sql_query.py +1 -0
- reconcile/status.py +2 -2
- reconcile/status_board.py +6 -6
- reconcile/statuspage/atlassian.py +7 -7
- reconcile/statuspage/integrations/maintenances.py +4 -3
- reconcile/statuspage/page.py +4 -9
- reconcile/statuspage/status.py +5 -8
- reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
- reconcile/templating/lib/merge_request_manager.py +2 -2
- reconcile/templating/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +12 -13
- reconcile/terraform_aws_route53.py +18 -8
- reconcile/terraform_cloudflare_dns.py +3 -3
- reconcile/terraform_cloudflare_resources.py +12 -13
- reconcile/terraform_cloudflare_users.py +3 -2
- reconcile/terraform_init/integration.py +187 -23
- reconcile/terraform_repo.py +16 -12
- reconcile/terraform_resources.py +18 -10
- reconcile/terraform_tgw_attachments.py +27 -19
- reconcile/terraform_users.py +29 -21
- reconcile/terraform_vpc_peerings.py +16 -4
- reconcile/terraform_vpc_resources/integration.py +32 -2
- reconcile/typed_queries/app_interface_roles.py +10 -0
- reconcile/typed_queries/aws_account_tags.py +41 -0
- reconcile/typed_queries/cost_report/app_names.py +1 -1
- reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
- reconcile/typed_queries/saas_files.py +13 -13
- reconcile/typed_queries/status_board.py +2 -2
- reconcile/unleash_feature_toggles/integration.py +4 -2
- reconcile/utils/acs/base.py +6 -3
- reconcile/utils/acs/policies.py +2 -2
- reconcile/utils/aggregated_list.py +4 -3
- reconcile/utils/aws_api.py +51 -20
- reconcile/utils/aws_api_typed/api.py +38 -9
- reconcile/utils/aws_api_typed/cloudformation.py +149 -0
- reconcile/utils/aws_api_typed/logs.py +73 -0
- reconcile/utils/aws_api_typed/organization.py +4 -2
- reconcile/utils/binary.py +7 -12
- reconcile/utils/datetime_util.py +67 -0
- reconcile/utils/deadmanssnitch_api.py +1 -1
- reconcile/utils/differ.py +2 -3
- reconcile/utils/early_exit_cache.py +11 -12
- reconcile/utils/expiration.py +7 -3
- reconcile/utils/external_resource_spec.py +24 -1
- reconcile/utils/filtering.py +1 -1
- reconcile/utils/gitlab_api.py +7 -5
- reconcile/utils/glitchtip/client.py +6 -2
- reconcile/utils/glitchtip/models.py +25 -28
- reconcile/utils/gpg.py +5 -3
- reconcile/utils/gql.py +4 -7
- reconcile/utils/helm.py +2 -1
- reconcile/utils/helpers.py +1 -1
- reconcile/utils/imap_client.py +1 -1
- reconcile/utils/instrumented_wrappers.py +1 -1
- reconcile/utils/internal_groups/client.py +2 -2
- reconcile/utils/internal_groups/models.py +8 -17
- reconcile/utils/jenkins_api.py +24 -1
- reconcile/utils/jinja2/utils.py +6 -8
- reconcile/utils/jira_client.py +82 -63
- reconcile/utils/jjb_client.py +59 -43
- reconcile/utils/jobcontroller/controller.py +2 -2
- reconcile/utils/jobcontroller/models.py +17 -1
- reconcile/utils/json.py +74 -0
- reconcile/utils/ldap_client.py +4 -3
- reconcile/utils/lean_terraform_client.py +3 -1
- reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
- reconcile/utils/membershipsources/models.py +16 -23
- reconcile/utils/membershipsources/resolver.py +4 -2
- reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
- reconcile/utils/merge_request_manager/parser.py +6 -6
- reconcile/utils/metrics.py +5 -5
- reconcile/utils/models.py +304 -82
- reconcile/utils/mr/__init__.py +3 -1
- reconcile/utils/mr/app_interface_reporter.py +6 -3
- reconcile/utils/mr/aws_access.py +1 -1
- reconcile/utils/mr/base.py +7 -13
- reconcile/utils/mr/clusters_updates.py +4 -2
- reconcile/utils/mr/notificator.py +3 -3
- reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +4 -1
- reconcile/utils/mr/promote_qontract.py +28 -12
- reconcile/utils/mr/update_access_report_base.py +3 -4
- reconcile/utils/mr/user_maintenance.py +7 -6
- reconcile/utils/oc.py +445 -336
- reconcile/utils/oc_filters.py +3 -3
- reconcile/utils/ocm/addons.py +0 -1
- reconcile/utils/ocm/base.py +27 -20
- reconcile/utils/ocm/cluster_groups.py +1 -1
- reconcile/utils/ocm/identity_providers.py +2 -2
- reconcile/utils/ocm/labels.py +1 -1
- reconcile/utils/ocm/ocm.py +81 -71
- reconcile/utils/ocm/products.py +9 -3
- reconcile/utils/ocm/search_filters.py +3 -6
- reconcile/utils/ocm/service_log.py +4 -6
- reconcile/utils/ocm/sre_capability_labels.py +20 -13
- reconcile/utils/ocm_base_client.py +4 -4
- reconcile/utils/openshift_resource.py +83 -52
- reconcile/utils/openssl.py +2 -2
- reconcile/utils/output.py +3 -2
- reconcile/utils/pagerduty_api.py +10 -7
- reconcile/utils/promotion_state.py +6 -11
- reconcile/utils/raw_github_api.py +11 -8
- reconcile/utils/repo_owners.py +21 -29
- reconcile/utils/rhcsv2_certs.py +138 -35
- reconcile/utils/rosa/session.py +16 -0
- reconcile/utils/runtime/integration.py +2 -3
- reconcile/utils/runtime/meta.py +2 -1
- reconcile/utils/runtime/runner.py +2 -2
- reconcile/utils/saasherder/interfaces.py +13 -20
- reconcile/utils/saasherder/models.py +25 -21
- reconcile/utils/saasherder/saasherder.py +60 -32
- reconcile/utils/secret_reader.py +6 -6
- reconcile/utils/sharding.py +1 -1
- reconcile/utils/slack_api.py +26 -4
- reconcile/utils/sloth.py +224 -0
- reconcile/utils/sqs_gateway.py +16 -11
- reconcile/utils/state.py +2 -1
- reconcile/utils/structs.py +4 -4
- reconcile/utils/terraform_client.py +32 -29
- reconcile/utils/terrascript_aws_client.py +658 -480
- reconcile/utils/three_way_diff_strategy.py +1 -1
- reconcile/utils/throughput.py +1 -1
- reconcile/utils/unleash/server.py +2 -8
- reconcile/utils/vault.py +44 -41
- reconcile/utils/vcs.py +8 -8
- reconcile/vault_replication.py +119 -58
- reconcile/vpc_peerings_validator.py +2 -2
- tools/app_interface_reporter.py +4 -4
- tools/cli_commands/cost_report/cost_management_api.py +3 -3
- tools/cli_commands/cost_report/view.py +7 -6
- tools/cli_commands/erv2.py +1 -1
- tools/cli_commands/gpg_encrypt.py +4 -1
- tools/cli_commands/systems_and_tools.py +5 -1
- tools/qontract_cli.py +36 -21
- tools/sre_checkpoints/util.py +5 -3
- tools/template_validation.py +3 -1
- reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
- reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
- reconcile/jenkins/__init__.py +0 -0
- reconcile/jenkins/types.py +0 -77
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev299.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/entry_points.txt +0 -0
|
@@ -61,7 +61,7 @@ def normalize_object(item: OR) -> OR:
|
|
|
61
61
|
validate_k8s_object=False,
|
|
62
62
|
)
|
|
63
63
|
|
|
64
|
-
annotations = n.body.get("metadata").get("annotations") or {}
|
|
64
|
+
annotations = n.body.get("metadata", {}).get("annotations") or {}
|
|
65
65
|
metadata["annotations"] = {
|
|
66
66
|
k: v for k, v in annotations.items() if k not in NORMALIZE_IGNORE_ANNOTATIONS
|
|
67
67
|
}
|
reconcile/utils/throughput.py
CHANGED
|
@@ -24,30 +24,24 @@ class Environment(BaseModel):
|
|
|
24
24
|
return self.name == other
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class FeatureToggle(BaseModel):
|
|
27
|
+
class FeatureToggle(BaseModel, validate_by_name=True, validate_by_alias=True):
|
|
28
28
|
name: str
|
|
29
29
|
type: FeatureToggleType = FeatureToggleType.release
|
|
30
30
|
description: str | None = None
|
|
31
31
|
impression_data: bool = Field(False, alias="impressionData")
|
|
32
32
|
environments: list[Environment]
|
|
33
33
|
|
|
34
|
-
class Config:
|
|
35
|
-
allow_population_by_field_name = True
|
|
36
|
-
|
|
37
34
|
def __eq__(self, other: object) -> bool:
|
|
38
35
|
if isinstance(other, FeatureToggle):
|
|
39
36
|
return self.name == other.name
|
|
40
37
|
return self.name == other
|
|
41
38
|
|
|
42
39
|
|
|
43
|
-
class Project(BaseModel):
|
|
40
|
+
class Project(BaseModel, validate_by_name=True, validate_by_alias=True):
|
|
44
41
|
pk: str = Field(alias="id")
|
|
45
42
|
name: str
|
|
46
43
|
feature_toggles: list[FeatureToggle] = []
|
|
47
44
|
|
|
48
|
-
class Config:
|
|
49
|
-
allow_population_by_field_name = True
|
|
50
|
-
|
|
51
45
|
|
|
52
46
|
class TokenAuth(BearerTokenAuth):
|
|
53
47
|
def __call__(self, r: requests.PreparedRequest) -> requests.PreparedRequest:
|
reconcile/utils/vault.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import base64
|
|
2
|
+
import builtins
|
|
2
3
|
import logging
|
|
3
4
|
import os
|
|
4
5
|
import threading
|
|
5
6
|
import time
|
|
6
7
|
from collections.abc import Mapping
|
|
7
8
|
from functools import lru_cache
|
|
9
|
+
from typing import Any, Self
|
|
8
10
|
|
|
9
11
|
import hvac
|
|
10
12
|
import requests
|
|
@@ -49,7 +51,7 @@ class VaultConnectionError(Exception):
|
|
|
49
51
|
SECRET_VERSION_LATEST = "LATEST"
|
|
50
52
|
|
|
51
53
|
|
|
52
|
-
class
|
|
54
|
+
class VaultClient:
|
|
53
55
|
"""
|
|
54
56
|
A class representing a Vault client. Allows read/write operations.
|
|
55
57
|
The client caches read requests in-memory if the request is made
|
|
@@ -57,6 +59,28 @@ class _VaultClient:
|
|
|
57
59
|
and a version (no invalidation required).
|
|
58
60
|
"""
|
|
59
61
|
|
|
62
|
+
_instance = None
|
|
63
|
+
_lock = threading.Lock()
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def get_instance(cls) -> Self:
|
|
67
|
+
with cls._lock:
|
|
68
|
+
if cls._instance is None:
|
|
69
|
+
cls._instance = cls()
|
|
70
|
+
return cls._instance
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
is_authenticated = cls._instance._client.is_authenticated()
|
|
74
|
+
except requests.exceptions.ConnectionError:
|
|
75
|
+
is_authenticated = False
|
|
76
|
+
|
|
77
|
+
if is_authenticated:
|
|
78
|
+
return cls._instance
|
|
79
|
+
|
|
80
|
+
cls._instance.close()
|
|
81
|
+
cls._instance = cls()
|
|
82
|
+
return cls._instance
|
|
83
|
+
|
|
60
84
|
def __init__(
|
|
61
85
|
self,
|
|
62
86
|
server: str | None = None,
|
|
@@ -122,13 +146,13 @@ class _VaultClient:
|
|
|
122
146
|
t = threading.Thread(target=self._auto_refresh_client_auth, daemon=True)
|
|
123
147
|
t.start()
|
|
124
148
|
|
|
125
|
-
def __enter__(self):
|
|
149
|
+
def __enter__(self) -> Self:
|
|
126
150
|
return self
|
|
127
151
|
|
|
128
|
-
def __exit__(self, *exc):
|
|
152
|
+
def __exit__(self, *exc: Any) -> None:
|
|
129
153
|
self.close()
|
|
130
154
|
|
|
131
|
-
def close(self):
|
|
155
|
+
def close(self) -> None:
|
|
132
156
|
"""
|
|
133
157
|
Close the client and release any resources associated with it.
|
|
134
158
|
"""
|
|
@@ -137,7 +161,7 @@ class _VaultClient:
|
|
|
137
161
|
self._client.adapter.close()
|
|
138
162
|
self._closed = True
|
|
139
163
|
|
|
140
|
-
def _auto_refresh_client_auth(self):
|
|
164
|
+
def _auto_refresh_client_auth(self) -> None:
|
|
141
165
|
"""
|
|
142
166
|
Thread that periodically refreshes the vault token
|
|
143
167
|
"""
|
|
@@ -150,7 +174,7 @@ class _VaultClient:
|
|
|
150
174
|
LOG.debug("auto refresh client auth")
|
|
151
175
|
self._refresh_client_auth()
|
|
152
176
|
|
|
153
|
-
def _refresh_client_auth(self):
|
|
177
|
+
def _refresh_client_auth(self) -> None:
|
|
154
178
|
if self.kube_auth_enabled:
|
|
155
179
|
# must read each time to account for sa token refresh
|
|
156
180
|
with open(self.kube_sa_token_path, encoding="locale") as f:
|
|
@@ -166,7 +190,7 @@ class _VaultClient:
|
|
|
166
190
|
self._client.auth_approle(self.role_id, self.secret_id)
|
|
167
191
|
|
|
168
192
|
@retry()
|
|
169
|
-
def read_all_with_version(self, secret: Mapping) -> tuple[
|
|
193
|
+
def read_all_with_version(self, secret: Mapping) -> tuple[dict, int | None]:
|
|
170
194
|
"""Returns a dictionary of keys and values in a Vault secret and the
|
|
171
195
|
version of the secret, for V1 secrets, version will be None.
|
|
172
196
|
|
|
@@ -176,7 +200,7 @@ class _VaultClient:
|
|
|
176
200
|
a v2 KV engine)
|
|
177
201
|
"""
|
|
178
202
|
secret_path = secret["path"]
|
|
179
|
-
secret_version = secret.get("version")
|
|
203
|
+
secret_version = secret.get("version") or SECRET_VERSION_LATEST
|
|
180
204
|
|
|
181
205
|
kv_version = self._get_mount_version_by_secret_path(secret_path)
|
|
182
206
|
|
|
@@ -203,12 +227,12 @@ class _VaultClient:
|
|
|
203
227
|
"""
|
|
204
228
|
return self.read_all_with_version(secret)[0]
|
|
205
229
|
|
|
206
|
-
def _get_mount_version_by_secret_path(self, path):
|
|
230
|
+
def _get_mount_version_by_secret_path(self, path: str) -> int:
|
|
207
231
|
path_split = path.split("/")
|
|
208
232
|
mount_point = path_split[0]
|
|
209
233
|
return self._get_mount_version(mount_point)
|
|
210
234
|
|
|
211
|
-
def __get_mount_version(self, mount_point):
|
|
235
|
+
def __get_mount_version(self, mount_point: str) -> int:
|
|
212
236
|
try:
|
|
213
237
|
self._client.secrets.kv.v2.read_configuration(mount_point)
|
|
214
238
|
version = 2
|
|
@@ -217,7 +241,9 @@ class _VaultClient:
|
|
|
217
241
|
|
|
218
242
|
return version
|
|
219
243
|
|
|
220
|
-
def __read_all_v2(
|
|
244
|
+
def __read_all_v2(
|
|
245
|
+
self, path: str, version: str | None
|
|
246
|
+
) -> tuple[dict[str, Any], int]:
|
|
221
247
|
path_split = path.split("/")
|
|
222
248
|
mount_point = path_split[0]
|
|
223
249
|
read_path = "/".join(path_split[1:])
|
|
@@ -248,7 +274,7 @@ class _VaultClient:
|
|
|
248
274
|
secret_version = secret["data"]["metadata"]["version"]
|
|
249
275
|
return data, secret_version
|
|
250
276
|
|
|
251
|
-
def _read_all_v1(self, path):
|
|
277
|
+
def _read_all_v1(self, path: str) -> Any:
|
|
252
278
|
try:
|
|
253
279
|
secret = self._client.read(path)
|
|
254
280
|
except hvac.exceptions.Forbidden:
|
|
@@ -261,7 +287,7 @@ class _VaultClient:
|
|
|
261
287
|
return secret["data"]
|
|
262
288
|
|
|
263
289
|
@retry()
|
|
264
|
-
def read(self, secret):
|
|
290
|
+
def read(self, secret: Mapping[str, Any]) -> Any:
|
|
265
291
|
"""Returns a value of a key in a Vault secret.
|
|
266
292
|
|
|
267
293
|
The input secret is a dictionary which contains the following fields:
|
|
@@ -293,7 +319,7 @@ class _VaultClient:
|
|
|
293
319
|
else data
|
|
294
320
|
)
|
|
295
321
|
|
|
296
|
-
def _read_v2(self, path, field, version):
|
|
322
|
+
def _read_v2(self, path: str, field: str, version: str | None) -> Any:
|
|
297
323
|
data, _ = self._read_all_v2(path, version)
|
|
298
324
|
try:
|
|
299
325
|
secret_field = data[field]
|
|
@@ -301,7 +327,7 @@ class _VaultClient:
|
|
|
301
327
|
raise SecretFieldNotFoundError(f"{path}/{field} ({version})") from None
|
|
302
328
|
return secret_field
|
|
303
329
|
|
|
304
|
-
def _read_v1(self, path, field):
|
|
330
|
+
def _read_v1(self, path: str, field: str) -> Any:
|
|
305
331
|
data = self._read_all_v1(path)
|
|
306
332
|
try:
|
|
307
333
|
secret_field = data[field]
|
|
@@ -360,7 +386,7 @@ class _VaultClient:
|
|
|
360
386
|
msg = f"permission denied accessing secret '{path}'"
|
|
361
387
|
raise SecretAccessForbiddenError(msg) from None
|
|
362
388
|
|
|
363
|
-
def _write_v1(self, path, data):
|
|
389
|
+
def _write_v1(self, path: str, data: dict[str, Any]) -> None:
|
|
364
390
|
try:
|
|
365
391
|
self._client.write(path, **data)
|
|
366
392
|
except hvac.exceptions.Forbidden:
|
|
@@ -395,7 +421,7 @@ class _VaultClient:
|
|
|
395
421
|
return []
|
|
396
422
|
return path_list["data"]["keys"] or []
|
|
397
423
|
|
|
398
|
-
def list_all(self, path):
|
|
424
|
+
def list_all(self, path: str) -> builtins.list[str]:
|
|
399
425
|
"""Returns a list of secrets in a given path and
|
|
400
426
|
all its subpaths."""
|
|
401
427
|
secrets = []
|
|
@@ -415,32 +441,9 @@ class _VaultClient:
|
|
|
415
441
|
raise ValueError("deleting V2 secrets is not supported yet")
|
|
416
442
|
self._delete_v1(path)
|
|
417
443
|
|
|
418
|
-
def _delete_v1(self, path):
|
|
444
|
+
def _delete_v1(self, path: str) -> None:
|
|
419
445
|
try:
|
|
420
446
|
self._client.delete(path)
|
|
421
447
|
except hvac.exceptions.Forbidden:
|
|
422
448
|
msg = f"permission denied accessing secret '{path}'"
|
|
423
449
|
raise SecretAccessForbiddenError(msg) from None
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
class VaultClient:
|
|
427
|
-
_instance_lock = threading.Lock()
|
|
428
|
-
_instance = None
|
|
429
|
-
|
|
430
|
-
def __new__(cls, *args, **kwargs):
|
|
431
|
-
with cls._instance_lock:
|
|
432
|
-
if cls._instance is None:
|
|
433
|
-
cls._instance = _VaultClient(*args, **kwargs)
|
|
434
|
-
return cls._instance
|
|
435
|
-
|
|
436
|
-
try:
|
|
437
|
-
is_authenticated = cls._instance._client.is_authenticated()
|
|
438
|
-
except requests.exceptions.ConnectionError:
|
|
439
|
-
is_authenticated = False
|
|
440
|
-
|
|
441
|
-
if not is_authenticated:
|
|
442
|
-
cls._instance.close()
|
|
443
|
-
cls._instance = _VaultClient(*args, **kwargs)
|
|
444
|
-
return cls._instance
|
|
445
|
-
|
|
446
|
-
return cls._instance
|
reconcile/utils/vcs.py
CHANGED
|
@@ -140,7 +140,7 @@ class VCS:
|
|
|
140
140
|
gitlab_instances: Iterable[GitlabInstanceV1],
|
|
141
141
|
) -> GitLabApi:
|
|
142
142
|
return GitLabApi(
|
|
143
|
-
next(iter(gitlab_instances)).
|
|
143
|
+
next(iter(gitlab_instances)).model_dump(by_alias=True),
|
|
144
144
|
secret_reader=self._secret_reader,
|
|
145
145
|
)
|
|
146
146
|
|
|
@@ -150,7 +150,7 @@ class VCS:
|
|
|
150
150
|
app_interface_repo_url: str,
|
|
151
151
|
) -> GitLabApi:
|
|
152
152
|
return GitLabApi(
|
|
153
|
-
next(iter(gitlab_instances)).
|
|
153
|
+
next(iter(gitlab_instances)).model_dump(by_alias=True),
|
|
154
154
|
secret_reader=self._secret_reader,
|
|
155
155
|
project_url=app_interface_repo_url,
|
|
156
156
|
)
|
|
@@ -221,26 +221,26 @@ class VCS:
|
|
|
221
221
|
match repo_info.platform:
|
|
222
222
|
case "github":
|
|
223
223
|
github = self._init_github(repo_url=repo_url, auth_code=auth_code)
|
|
224
|
-
data = github.compare(commit_from=commit_from, commit_to=commit_to)
|
|
225
224
|
return [
|
|
226
225
|
Commit(
|
|
227
226
|
repo=repo_url,
|
|
228
227
|
sha=gh_commit.sha,
|
|
229
228
|
date=gh_commit.commit.committer.date,
|
|
230
229
|
)
|
|
231
|
-
for gh_commit in
|
|
230
|
+
for gh_commit in github.compare(
|
|
231
|
+
commit_from=commit_from, commit_to=commit_to
|
|
232
|
+
)
|
|
232
233
|
]
|
|
233
234
|
case "gitlab":
|
|
234
|
-
data = self._gitlab_instance.repository_compare(
|
|
235
|
-
repo_url=repo_url, ref_from=commit_from, ref_to=commit_to
|
|
236
|
-
)
|
|
237
235
|
return [
|
|
238
236
|
Commit(
|
|
239
237
|
repo=repo_url,
|
|
240
238
|
sha=gl_commit["id"],
|
|
241
239
|
date=datetime.fromisoformat(gl_commit["committed_date"]),
|
|
242
240
|
)
|
|
243
|
-
for gl_commit in
|
|
241
|
+
for gl_commit in self._gitlab_instance.repository_compare(
|
|
242
|
+
repo_url=repo_url, ref_from=commit_from, ref_to=commit_to
|
|
243
|
+
)
|
|
244
244
|
]
|
|
245
245
|
case _:
|
|
246
246
|
raise ValueError(f"Unsupported repository URL: {repo_url}")
|
reconcile/vault_replication.py
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import re
|
|
3
3
|
from collections.abc import Iterable
|
|
4
|
-
from typing import (
|
|
5
|
-
cast,
|
|
6
|
-
)
|
|
7
4
|
|
|
8
5
|
from reconcile.gql_definitions.jenkins_configs import jenkins_configs
|
|
9
6
|
from reconcile.gql_definitions.jenkins_configs.jenkins_configs import (
|
|
@@ -30,7 +27,6 @@ from reconcile.utils.vault import (
|
|
|
30
27
|
SecretNotFoundError,
|
|
31
28
|
SecretVersionNotFoundError,
|
|
32
29
|
VaultClient,
|
|
33
|
-
_VaultClient,
|
|
34
30
|
)
|
|
35
31
|
|
|
36
32
|
QONTRACT_INTEGRATION = "vault-replication"
|
|
@@ -51,8 +47,8 @@ class VaultInvalidPolicyError(Exception):
|
|
|
51
47
|
|
|
52
48
|
def deep_copy_versions(
|
|
53
49
|
dry_run: bool,
|
|
54
|
-
source_vault:
|
|
55
|
-
dest_vault:
|
|
50
|
+
source_vault: VaultClient,
|
|
51
|
+
dest_vault: VaultClient,
|
|
56
52
|
current_dest_version: int,
|
|
57
53
|
current_source_version: int,
|
|
58
54
|
path: str,
|
|
@@ -88,9 +84,57 @@ def deep_copy_versions(
|
|
|
88
84
|
dest_vault.write(secret=write_dict, decode_base64=False, force=True)
|
|
89
85
|
|
|
90
86
|
|
|
87
|
+
def _handle_missing_destination_secret(
|
|
88
|
+
dry_run: bool,
|
|
89
|
+
source_vault: VaultClient,
|
|
90
|
+
dest_vault: VaultClient,
|
|
91
|
+
source_data: dict,
|
|
92
|
+
source_version: int | None,
|
|
93
|
+
path: str,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Handles replication when destination secret is missing or has no accessible versions.
|
|
96
|
+
|
|
97
|
+
This covers two scenarios:
|
|
98
|
+
1. Secret doesn't exist at all in destination vault (SecretNotFoundError)
|
|
99
|
+
2. Secret exists but all versions are deleted in KV v2 (SecretVersionNotFoundError)
|
|
100
|
+
|
|
101
|
+
For both cases, we replicate from source starting from version 0 (or copy directly for v1).
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
dry_run: Whether this is a dry run
|
|
105
|
+
source_vault: Source vault client (needed for v2 deep copy)
|
|
106
|
+
dest_vault: Destination vault client
|
|
107
|
+
source_data: Already retrieved source secret data
|
|
108
|
+
source_version: Source secret version (None for v1 secrets)
|
|
109
|
+
path: Secret path
|
|
110
|
+
"""
|
|
111
|
+
if source_version is None:
|
|
112
|
+
# v1 secret - just copy it over using the already-retrieved source data
|
|
113
|
+
logging.info(["replicate_vault_secret", "Copying v1 secret", path])
|
|
114
|
+
if not dry_run:
|
|
115
|
+
write_dict = {"path": path, "data": source_data}
|
|
116
|
+
dest_vault.write(secret=write_dict, decode_base64=False, force=True)
|
|
117
|
+
else:
|
|
118
|
+
# v2 secret - deep copy all versions starting from 0
|
|
119
|
+
# Note: deep_copy_versions will read individual versions from source as needed
|
|
120
|
+
logging.info([
|
|
121
|
+
"replicate_vault_secret",
|
|
122
|
+
"Deep copying v2 secret versions",
|
|
123
|
+
path,
|
|
124
|
+
])
|
|
125
|
+
deep_copy_versions(
|
|
126
|
+
dry_run=dry_run,
|
|
127
|
+
source_vault=source_vault,
|
|
128
|
+
dest_vault=dest_vault,
|
|
129
|
+
current_dest_version=0,
|
|
130
|
+
current_source_version=source_version,
|
|
131
|
+
path=path,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
91
135
|
def write_dummy_versions(
|
|
92
136
|
dry_run: bool,
|
|
93
|
-
dest_vault:
|
|
137
|
+
dest_vault: VaultClient,
|
|
94
138
|
secret_version: int,
|
|
95
139
|
path: str,
|
|
96
140
|
) -> None:
|
|
@@ -112,7 +156,7 @@ def write_dummy_versions(
|
|
|
112
156
|
|
|
113
157
|
|
|
114
158
|
def copy_vault_secret(
|
|
115
|
-
dry_run: bool, source_vault:
|
|
159
|
+
dry_run: bool, source_vault: VaultClient, dest_vault: VaultClient, path: str
|
|
116
160
|
) -> None:
|
|
117
161
|
"""Copies a secret from the source vault to the destination vault"""
|
|
118
162
|
secret_dict = {"path": path, "version": "LATEST"}
|
|
@@ -137,48 +181,65 @@ def copy_vault_secret(
|
|
|
137
181
|
|
|
138
182
|
try:
|
|
139
183
|
dest_data, dest_version = dest_vault.read_all_with_version(secret_dict)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
184
|
+
except SecretVersionNotFoundError:
|
|
185
|
+
# Handle KV v2 case where secret metadata exists but latest version is deleted
|
|
186
|
+
# This occurs when someone manually deletes the latest version but the secret
|
|
187
|
+
# metadata still exists in Vault. This should only happen for v2 secrets.
|
|
188
|
+
logging.info([
|
|
189
|
+
"replicate_vault_secret",
|
|
190
|
+
"KV v2 latest version deleted, replicating all versions",
|
|
191
|
+
path,
|
|
192
|
+
])
|
|
193
|
+
_handle_missing_destination_secret(
|
|
194
|
+
dry_run=dry_run,
|
|
195
|
+
source_vault=source_vault,
|
|
196
|
+
dest_vault=dest_vault,
|
|
197
|
+
source_data=source_data,
|
|
198
|
+
source_version=version,
|
|
199
|
+
path=path,
|
|
200
|
+
)
|
|
201
|
+
return
|
|
202
|
+
except SecretNotFoundError:
|
|
203
|
+
# Handle case where secret doesn't exist at all in destination vault
|
|
204
|
+
logging.info([
|
|
205
|
+
"replicate_vault_secret",
|
|
206
|
+
"Secret not found in destination",
|
|
207
|
+
path,
|
|
208
|
+
])
|
|
209
|
+
_handle_missing_destination_secret(
|
|
210
|
+
dry_run=dry_run,
|
|
211
|
+
source_vault=source_vault,
|
|
212
|
+
dest_vault=dest_vault,
|
|
213
|
+
source_data=source_data,
|
|
214
|
+
source_version=version,
|
|
215
|
+
path=path,
|
|
216
|
+
)
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
# If we reach here, we successfully read the destination secret
|
|
220
|
+
if dest_version is None or version is None:
|
|
221
|
+
# v1 secrets don't have version
|
|
222
|
+
if source_data == dest_data:
|
|
223
|
+
# If the secret is the same in both vaults, we don't need
|
|
224
|
+
# to copy it again
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
write_dict = {"path": path, "data": source_data}
|
|
228
|
+
logging.info(["replicate_vault_secret", path])
|
|
229
|
+
if not dry_run:
|
|
230
|
+
# Using force=True to write the secret to force the vault client even
|
|
231
|
+
# if the data is the same as the previous version. This happens in
|
|
232
|
+
# some secrets even tho the library does not create it
|
|
233
|
+
dest_vault.write(secret=write_dict, decode_base64=False, force=True)
|
|
234
|
+
elif dest_version < version:
|
|
235
|
+
deep_copy_versions(
|
|
236
|
+
dry_run=dry_run,
|
|
237
|
+
source_vault=source_vault,
|
|
238
|
+
dest_vault=dest_vault,
|
|
239
|
+
current_dest_version=dest_version,
|
|
240
|
+
current_source_version=version,
|
|
241
|
+
path=path,
|
|
242
|
+
)
|
|
182
243
|
|
|
183
244
|
|
|
184
245
|
def check_invalid_paths(
|
|
@@ -228,7 +289,7 @@ def get_policy_paths(
|
|
|
228
289
|
|
|
229
290
|
|
|
230
291
|
def get_policy_secret_list(
|
|
231
|
-
vault_instance:
|
|
292
|
+
vault_instance: VaultClient, policy_paths: Iterable[str]
|
|
232
293
|
) -> list[str]:
|
|
233
294
|
"""Returns a list of secrets to be copied from the given policy"""
|
|
234
295
|
secrets = set()
|
|
@@ -249,7 +310,7 @@ def get_policy_secret_list(
|
|
|
249
310
|
|
|
250
311
|
|
|
251
312
|
def get_jenkins_secret_list(
|
|
252
|
-
vault_instance:
|
|
313
|
+
vault_instance: VaultClient,
|
|
253
314
|
jenkins_instance: str,
|
|
254
315
|
query_data: JenkinsConfigsQueryData,
|
|
255
316
|
) -> list[str]:
|
|
@@ -294,7 +355,7 @@ def get_vault_credentials(
|
|
|
294
355
|
"""Returns a dictionary with the credentials used to authenticate with Vault,
|
|
295
356
|
retrieved from the values present on AppInterface and comming from Vault itself."""
|
|
296
357
|
vault_creds = {}
|
|
297
|
-
vault =
|
|
358
|
+
vault = VaultClient.get_instance()
|
|
298
359
|
|
|
299
360
|
if not isinstance(
|
|
300
361
|
vault_auth,
|
|
@@ -324,8 +385,8 @@ def get_vault_credentials(
|
|
|
324
385
|
|
|
325
386
|
def replicate_paths(
|
|
326
387
|
dry_run: bool,
|
|
327
|
-
source_vault:
|
|
328
|
-
dest_vault:
|
|
388
|
+
source_vault: VaultClient,
|
|
389
|
+
dest_vault: VaultClient,
|
|
329
390
|
replications: VaultReplicationConfigV1,
|
|
330
391
|
) -> None:
|
|
331
392
|
"""For each path present in the definition of the vault instance, replicate
|
|
@@ -435,16 +496,16 @@ def run(dry_run: bool) -> None:
|
|
|
435
496
|
replication.dest_auth, replication.vault_instance.address
|
|
436
497
|
)
|
|
437
498
|
|
|
438
|
-
# Private class
|
|
499
|
+
# Private class VaultClient is used because the public class is
|
|
439
500
|
# defined as a singleton, and we need to create multiple instances
|
|
440
501
|
# as the source vault is different than the replication.
|
|
441
502
|
with (
|
|
442
|
-
|
|
503
|
+
VaultClient(
|
|
443
504
|
server=source_creds["server"],
|
|
444
505
|
role_id=source_creds["role_id"],
|
|
445
506
|
secret_id=source_creds["secret_id"],
|
|
446
507
|
) as source_vault,
|
|
447
|
-
|
|
508
|
+
VaultClient(
|
|
448
509
|
server=dest_creds["server"],
|
|
449
510
|
role_id=dest_creds["role_id"],
|
|
450
511
|
secret_id=dest_creds["secret_id"],
|
|
@@ -79,7 +79,7 @@ def validate_no_cidr_overlap(
|
|
|
79
79
|
return True
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
def find_cidr_overlap(cluster_name: str, input_list: list):
|
|
82
|
+
def find_cidr_overlap(cluster_name: str, input_list: list) -> bool:
|
|
83
83
|
for i in range(len(input_list)):
|
|
84
84
|
compared_vpc = input_list[i]
|
|
85
85
|
for j in range(i + 1, len(input_list)):
|
|
@@ -171,7 +171,7 @@ def validate_no_public_to_public_peerings(
|
|
|
171
171
|
return valid
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
def run(dry_run: bool):
|
|
174
|
+
def run(dry_run: bool) -> None:
|
|
175
175
|
query_data = vpc_peerings_validator.query(query_func=gql.get_api().query)
|
|
176
176
|
|
|
177
177
|
valid = True
|
tools/app_interface_reporter.py
CHANGED
|
@@ -4,7 +4,6 @@ import os
|
|
|
4
4
|
import textwrap
|
|
5
5
|
from collections.abc import Mapping, MutableMapping
|
|
6
6
|
from datetime import (
|
|
7
|
-
UTC,
|
|
8
7
|
datetime,
|
|
9
8
|
)
|
|
10
9
|
|
|
@@ -29,6 +28,7 @@ from reconcile.cli import (
|
|
|
29
28
|
)
|
|
30
29
|
from reconcile.jenkins_job_builder import init_jjb
|
|
31
30
|
from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
|
|
31
|
+
from reconcile.utils.datetime_util import ensure_utc, utc_now
|
|
32
32
|
from reconcile.utils.mr import CreateAppInterfaceReporter
|
|
33
33
|
from reconcile.utils.runtime.environment import init_env
|
|
34
34
|
from reconcile.utils.secret_reader import SecretReader
|
|
@@ -189,8 +189,8 @@ def get_apps_data(
|
|
|
189
189
|
apps = queries.get_apps()
|
|
190
190
|
jjb = init_jjb(secret_reader)
|
|
191
191
|
jenkins_map = jenkins_base.get_jenkins_map()
|
|
192
|
-
time_limit = date - relativedelta(months=month_delta)
|
|
193
|
-
timestamp_limit = int(time_limit.
|
|
192
|
+
time_limit = ensure_utc(date) - relativedelta(months=month_delta)
|
|
193
|
+
timestamp_limit = int(time_limit.timestamp())
|
|
194
194
|
|
|
195
195
|
secret_content = secret_reader.read_all({"path": DASHDOTDB_SECRET})
|
|
196
196
|
dashdotdb_url = secret_content["url"]
|
|
@@ -411,7 +411,7 @@ def main(
|
|
|
411
411
|
) -> None:
|
|
412
412
|
init_env(log_level=log_level, config_file=configfile)
|
|
413
413
|
|
|
414
|
-
now =
|
|
414
|
+
now = utc_now()
|
|
415
415
|
apps = get_apps_data(now, thread_pool_size=thread_pool_size)
|
|
416
416
|
|
|
417
417
|
reports = [Report(app, now).to_message() for app in apps]
|
|
@@ -74,7 +74,7 @@ class CostManagementApi(ApiBase):
|
|
|
74
74
|
timeout=self.read_timeout,
|
|
75
75
|
)
|
|
76
76
|
response.raise_for_status()
|
|
77
|
-
return AwsReportCostResponse.
|
|
77
|
+
return AwsReportCostResponse.model_validate(response.json())
|
|
78
78
|
|
|
79
79
|
def get_openshift_costs_report(
|
|
80
80
|
self,
|
|
@@ -97,7 +97,7 @@ class CostManagementApi(ApiBase):
|
|
|
97
97
|
timeout=self.read_timeout,
|
|
98
98
|
)
|
|
99
99
|
response.raise_for_status()
|
|
100
|
-
return OpenShiftReportCostResponse.
|
|
100
|
+
return OpenShiftReportCostResponse.model_validate(response.json())
|
|
101
101
|
|
|
102
102
|
def get_openshift_cost_optimization_report(
|
|
103
103
|
self,
|
|
@@ -120,7 +120,7 @@ class CostManagementApi(ApiBase):
|
|
|
120
120
|
response.raise_for_status()
|
|
121
121
|
|
|
122
122
|
data = self._get_paginated(response)
|
|
123
|
-
return OpenShiftCostOptimizationReportResponse.
|
|
123
|
+
return OpenShiftCostOptimizationReportResponse.model_validate(data)
|
|
124
124
|
|
|
125
125
|
def _get_paginated(
|
|
126
126
|
self,
|