qontract-reconcile 0.10.2.dev361__py3-none-any.whl → 0.10.2.dev474__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.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/METADATA +14 -13
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/RECORD +371 -364
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/WHEEL +1 -1
- reconcile/acs_rbac.py +2 -2
- reconcile/aus/advanced_upgrade_service.py +18 -12
- reconcile/aus/aus_sts_gate_handler.py +59 -0
- reconcile/aus/base.py +137 -34
- 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_gate_approver.py +1 -16
- reconcile/aus/version_gates/sts_version_gate_handler.py +5 -72
- reconcile/automated_actions/config/integration.py +16 -4
- reconcile/aws_account_manager/integration.py +21 -9
- reconcile/aws_account_manager/reconciler.py +3 -3
- reconcile/aws_account_manager/utils.py +1 -1
- 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 +1 -1
- reconcile/aws_iam_keys.py +1 -0
- reconcile/aws_saml_idp/integration.py +12 -4
- reconcile/aws_saml_roles/integration.py +30 -23
- reconcile/aws_version_sync/integration.py +6 -12
- reconcile/change_owners/README.md +1 -1
- reconcile/change_owners/bundle.py +3 -3
- reconcile/change_owners/change_log_tracking.py +3 -2
- reconcile/change_owners/change_owners.py +108 -42
- reconcile/change_owners/decision.py +1 -1
- reconcile/change_owners/diff.py +0 -2
- reconcile/checkpoint.py +11 -3
- reconcile/cli.py +94 -11
- reconcile/dashdotdb_dora.py +5 -12
- reconcile/dashdotdb_slo.py +1 -1
- reconcile/database_access_manager.py +123 -117
- 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 +8 -8
- reconcile/external_resources/factories.py +4 -6
- reconcile/external_resources/integration.py +1 -1
- reconcile/external_resources/manager.py +8 -6
- reconcile/external_resources/meta.py +0 -1
- reconcile/external_resources/metrics.py +1 -1
- reconcile/external_resources/model.py +19 -15
- reconcile/external_resources/reconciler.py +7 -4
- reconcile/external_resources/secrets_sync.py +6 -10
- reconcile/external_resources/state.py +26 -16
- reconcile/fleet_labeler/integration.py +1 -1
- reconcile/gabi_authorized_users.py +5 -2
- 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_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 +5 -5
- reconcile/gql_definitions/acs/acs_policies.py +5 -5
- reconcile/gql_definitions/acs/acs_rbac.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
- reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
- 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 +46 -7
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +14 -5
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
- reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
- 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 +5 -5
- reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
- reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
- 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 +5 -5
- reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
- 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 +18 -5
- reconcile/gql_definitions/common/aws_vpcs.py +5 -5
- reconcile/gql_definitions/common/clusters.py +7 -5
- reconcile/gql_definitions/common/clusters_minimal.py +5 -5
- reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
- reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
- reconcile/gql_definitions/common/github_orgs.py +5 -5
- reconcile/gql_definitions/common/jira_settings.py +5 -5
- reconcile/gql_definitions/common/jiralert_settings.py +5 -5
- reconcile/gql_definitions/common/ldap_settings.py +5 -5
- reconcile/gql_definitions/common/namespaces.py +5 -5
- reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
- reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
- reconcile/gql_definitions/common/ocm_environments.py +5 -5
- reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
- reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
- reconcile/gql_definitions/common/pipeline_providers.py +5 -5
- 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 +5 -5
- reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
- 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 +5 -5
- reconcile/gql_definitions/common/state_aws_account.py +5 -5
- 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 +5 -5
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
- reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
- 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 +5 -5
- reconcile/gql_definitions/email_sender/users.py +5 -5
- reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
- reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +38 -7
- reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
- reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
- reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
- 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_organization.py +33 -0
- reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
- reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -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 +5 -5
- reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
- reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
- reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
- reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
- reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
- reconcile/gql_definitions/integrations/integrations.py +5 -5
- reconcile/gql_definitions/introspection.json +775 -136
- reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
- reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
- reconcile/gql_definitions/jira/jira_servers.py +5 -5
- reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
- reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
- reconcile/gql_definitions/ldap_groups/roles.py +5 -5
- reconcile/gql_definitions/ldap_groups/settings.py +5 -5
- reconcile/gql_definitions/maintenance/maintenances.py +5 -5
- reconcile/gql_definitions/membershipsources/roles.py +5 -5
- reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
- reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
- reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
- reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
- reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
- reconcile/gql_definitions/rhcs/certs.py +25 -79
- reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
- reconcile/gql_definitions/rhidp/organizations.py +5 -5
- reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
- reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
- reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
- reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
- reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
- reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
- reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
- reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
- 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 +5 -5
- reconcile/gql_definitions/statuspage/statuspages.py +5 -5
- 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 +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
- reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
- reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
- reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +37 -7
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
- reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
- reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
- reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +6 -5
- reconcile/integrations_manager.py +3 -3
- reconcile/jenkins_worker_fleets.py +10 -8
- reconcile/jira_permissions_validator.py +237 -122
- reconcile/ldap_groups/integration.py +1 -1
- reconcile/ocm/types.py +35 -57
- 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 +113 -4
- reconcile/openshift_cluster_bots.py +1 -1
- reconcile/openshift_namespace_labels.py +1 -1
- reconcile/openshift_namespaces.py +96 -101
- reconcile/openshift_resources_base.py +6 -2
- reconcile/openshift_rhcs_certs.py +74 -37
- reconcile/openshift_rolebindings.py +7 -11
- reconcile/openshift_saas_deploy.py +4 -5
- reconcile/openshift_saas_deploy_change_tester.py +9 -7
- reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
- reconcile/openshift_serviceaccount_tokens.py +2 -2
- reconcile/openshift_upgrade_watcher.py +4 -4
- reconcile/oum/labelset.py +5 -3
- reconcile/oum/models.py +1 -4
- reconcile/prometheus_rules_tester/integration.py +3 -3
- reconcile/quay_base.py +25 -6
- reconcile/quay_membership.py +55 -29
- reconcile/quay_mirror.py +1 -1
- reconcile/quay_mirror_org.py +6 -4
- reconcile/quay_permissions.py +81 -75
- reconcile/quay_repos.py +35 -37
- reconcile/queries.py +132 -1
- reconcile/rhidp/common.py +3 -5
- reconcile/rhidp/sso_client/base.py +16 -5
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
- reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
- reconcile/skupper_network/integration.py +2 -2
- reconcile/slack_usergroups.py +35 -14
- reconcile/sql_query.py +1 -0
- 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 +1 -1
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
- reconcile/templating/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +2 -2
- reconcile/templating/validator.py +4 -4
- reconcile/terraform_aws_route53.py +7 -1
- reconcile/terraform_cloudflare_dns.py +3 -3
- reconcile/terraform_cloudflare_resources.py +5 -5
- reconcile/terraform_cloudflare_users.py +3 -2
- reconcile/terraform_init/integration.py +187 -23
- reconcile/terraform_repo.py +16 -12
- reconcile/terraform_resources.py +6 -6
- reconcile/terraform_tgw_attachments.py +27 -19
- reconcile/terraform_users.py +7 -0
- reconcile/terraform_vpc_peerings.py +14 -3
- reconcile/terraform_vpc_resources/integration.py +20 -8
- reconcile/terraform_vpc_resources/merge_request.py +12 -2
- reconcile/terraform_vpc_resources/merge_request_manager.py +43 -19
- 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 +20 -15
- 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/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/environ.py +5 -0
- reconcile/utils/expiration.py +7 -3
- reconcile/utils/external_resource_spec.py +2 -0
- reconcile/utils/filtering.py +1 -1
- reconcile/utils/gitlab_api.py +19 -5
- reconcile/utils/glitchtip/client.py +6 -2
- reconcile/utils/glitchtip/models.py +25 -28
- reconcile/utils/gql.py +4 -7
- reconcile/utils/helpers.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/jinja2/utils.py +6 -101
- reconcile/utils/jira_client.py +82 -63
- reconcile/utils/jjb_client.py +26 -13
- reconcile/utils/jobcontroller/controller.py +2 -2
- reconcile/utils/jobcontroller/models.py +17 -1
- reconcile/utils/json.py +43 -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/app_interface_reporter.py +2 -2
- reconcile/utils/mr/notificator.py +3 -3
- reconcile/utils/mr/update_access_report_base.py +3 -4
- reconcile/utils/mr/user_maintenance.py +3 -2
- reconcile/utils/oc.py +252 -201
- reconcile/utils/oc_filters.py +3 -3
- reconcile/utils/ocm/addons.py +0 -1
- reconcile/utils/ocm/base.py +17 -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/products.py +8 -8
- 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/openshift_resource.py +8 -3
- reconcile/utils/pagerduty_api.py +10 -7
- reconcile/utils/promotion_state.py +6 -11
- reconcile/utils/quay_api.py +74 -87
- reconcile/utils/raw_github_api.py +1 -1
- reconcile/utils/rhcsv2_certs.py +86 -23
- reconcile/utils/rosa/session.py +16 -0
- reconcile/utils/runtime/integration.py +2 -3
- reconcile/utils/runtime/runner.py +2 -2
- reconcile/utils/saasherder/interfaces.py +13 -20
- reconcile/utils/saasherder/models.py +23 -20
- reconcile/utils/saasherder/saasherder.py +50 -27
- reconcile/utils/slack_api.py +2 -2
- reconcile/utils/sloth.py +171 -2
- reconcile/utils/structs.py +1 -1
- reconcile/utils/terraform_client.py +5 -4
- reconcile/utils/terrascript_aws_client.py +274 -124
- reconcile/utils/unleash/server.py +2 -8
- reconcile/utils/vault.py +5 -12
- reconcile/utils/vcs.py +8 -8
- reconcile/vault_replication.py +107 -42
- reconcile/vpc_peerings_validator.py +13 -0
- 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/qontract_cli.py +28 -17
- tools/template_validation.py +3 -1
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/entry_points.txt +0 -0
reconcile/utils/jira_client.py
CHANGED
|
@@ -17,10 +17,8 @@ from jira.resources import CustomFieldOption as JiraCustomFieldOption
|
|
|
17
17
|
from jira.resources import Resource
|
|
18
18
|
from pydantic import BaseModel
|
|
19
19
|
|
|
20
|
-
from reconcile.utils.secret_reader import SecretReader
|
|
21
|
-
|
|
22
20
|
if TYPE_CHECKING:
|
|
23
|
-
from collections.abc import Iterable
|
|
21
|
+
from collections.abc import Iterable
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
class JiraWatcherSettings(Protocol):
|
|
@@ -87,36 +85,18 @@ class IssueField(BaseModel):
|
|
|
87
85
|
options: list[FieldOption | CustomFieldOption]
|
|
88
86
|
|
|
89
87
|
|
|
88
|
+
CREATE_ISSUES = "CREATE_ISSUES"
|
|
89
|
+
TRANSITION_ISSUES = "TRANSITION_ISSUES"
|
|
90
|
+
PERMISSIONS = [CREATE_ISSUES, TRANSITION_ISSUES]
|
|
91
|
+
|
|
92
|
+
|
|
90
93
|
class JiraClient:
|
|
91
94
|
"""Wrapper around Jira client."""
|
|
92
95
|
|
|
93
96
|
DEFAULT_CONNECT_TIMEOUT = 60
|
|
94
97
|
DEFAULT_READ_TIMEOUT = 60
|
|
95
98
|
|
|
96
|
-
def __init__(
|
|
97
|
-
self,
|
|
98
|
-
jira_board: Mapping[str, Any] | None = None,
|
|
99
|
-
settings: Mapping | None = None,
|
|
100
|
-
jira_api: JIRA | None = None,
|
|
101
|
-
project: str | None = None,
|
|
102
|
-
server: str | None = None,
|
|
103
|
-
):
|
|
104
|
-
"""
|
|
105
|
-
Note: jira_board and settings is to be deprecated. Use JiraClient.create() instead.
|
|
106
|
-
"""
|
|
107
|
-
if jira_api and jira_board:
|
|
108
|
-
raise RuntimeError(
|
|
109
|
-
"jira_board parameter is deprecated. Use JiraClient.create() instead."
|
|
110
|
-
)
|
|
111
|
-
if not (jira_api and project):
|
|
112
|
-
# kept for backwards-compatibility
|
|
113
|
-
if not jira_board:
|
|
114
|
-
raise RuntimeError(
|
|
115
|
-
"JiraClient needs jira_api and project or jira_board."
|
|
116
|
-
)
|
|
117
|
-
self._deprecated_init(jira_board=jira_board, settings=settings)
|
|
118
|
-
return
|
|
119
|
-
|
|
99
|
+
def __init__(self, jira_api: JIRA, project: str, server: str):
|
|
120
100
|
self.server = server
|
|
121
101
|
self.project = project
|
|
122
102
|
self.jira = jira_api
|
|
@@ -128,47 +108,31 @@ class JiraClient:
|
|
|
128
108
|
self.project_issue_types = functools.cache(self._project_issue_types)
|
|
129
109
|
self.project_issue_fields = functools.cache(self._project_issue_fields)
|
|
130
110
|
|
|
131
|
-
def _deprecated_init(
|
|
132
|
-
self, jira_board: Mapping[str, Any], settings: Mapping | None
|
|
133
|
-
) -> None:
|
|
134
|
-
secret_reader = SecretReader(settings=settings)
|
|
135
|
-
self.project = jira_board["name"]
|
|
136
|
-
jira_server = jira_board["server"]
|
|
137
|
-
self.server = jira_server["serverUrl"]
|
|
138
|
-
token = jira_server["token"]
|
|
139
|
-
token_auth = secret_reader.read(token)
|
|
140
|
-
read_timeout = 60
|
|
141
|
-
connect_timeout = 60
|
|
142
|
-
if settings and settings["jiraWatcher"]:
|
|
143
|
-
read_timeout = settings["jiraWatcher"]["readTimeout"]
|
|
144
|
-
connect_timeout = settings["jiraWatcher"]["connectTimeout"]
|
|
145
|
-
if not self.server:
|
|
146
|
-
raise RuntimeError("JiraClient.server is not set.")
|
|
147
|
-
|
|
148
|
-
self.jira = JIRA(
|
|
149
|
-
self.server,
|
|
150
|
-
token_auth=token_auth,
|
|
151
|
-
timeout=(read_timeout, connect_timeout),
|
|
152
|
-
logging=False,
|
|
153
|
-
)
|
|
154
|
-
|
|
155
111
|
@staticmethod
|
|
156
112
|
def create(
|
|
157
113
|
project_name: str,
|
|
158
114
|
token: str,
|
|
115
|
+
email: str | None,
|
|
159
116
|
server_url: str,
|
|
160
117
|
jira_watcher_settings: JiraWatcherSettings | None = None,
|
|
161
118
|
) -> JiraClient:
|
|
119
|
+
"""Create a Jira client for the given project."""
|
|
162
120
|
read_timeout = JiraClient.DEFAULT_READ_TIMEOUT
|
|
163
121
|
connect_timeout = JiraClient.DEFAULT_CONNECT_TIMEOUT
|
|
164
122
|
if jira_watcher_settings:
|
|
165
123
|
read_timeout = jira_watcher_settings.read_timeout
|
|
166
124
|
connect_timeout = jira_watcher_settings.connect_timeout
|
|
125
|
+
|
|
126
|
+
# Jira Cloud uses email+API token for basic auth
|
|
127
|
+
# Jira Server/Data Center can use token auth (personal access token)
|
|
128
|
+
auth_params: dict[str, Any] = (
|
|
129
|
+
{"basic_auth": (email, token)} if email else {"token_auth": token}
|
|
130
|
+
)
|
|
167
131
|
jira_api = JIRA(
|
|
168
132
|
server=server_url,
|
|
169
|
-
token_auth=token,
|
|
170
133
|
timeout=(read_timeout, connect_timeout),
|
|
171
134
|
logging=False,
|
|
135
|
+
**auth_params,
|
|
172
136
|
)
|
|
173
137
|
return JiraClient(
|
|
174
138
|
jira_api=jira_api,
|
|
@@ -176,7 +140,13 @@ class JiraClient:
|
|
|
176
140
|
server=server_url,
|
|
177
141
|
)
|
|
178
142
|
|
|
143
|
+
@property
|
|
144
|
+
def is_cloud(self) -> bool:
|
|
145
|
+
"""Return whether we are on a Cloud based Jira instance."""
|
|
146
|
+
return self.jira.deploymentType == "Cloud"
|
|
147
|
+
|
|
179
148
|
def get_issues(self, fields: Iterable | None = None) -> list[Issue]:
|
|
149
|
+
"""Return all issues for our project."""
|
|
180
150
|
block_size = 100
|
|
181
151
|
block_num = 0
|
|
182
152
|
|
|
@@ -227,20 +197,34 @@ class JiraClient:
|
|
|
227
197
|
return issue
|
|
228
198
|
|
|
229
199
|
def _my_permissions(self, project: str) -> dict[str, Any]:
|
|
200
|
+
"""Return my permissions for the given project.
|
|
201
|
+
|
|
202
|
+
Don't use this function directly, use self.my_permissions which is cached."""
|
|
203
|
+
if self.is_cloud:
|
|
204
|
+
return self.jira.my_permissions(
|
|
205
|
+
projectKey=project, permissions=",".join(PERMISSIONS)
|
|
206
|
+
)["permissions"]
|
|
230
207
|
return self.jira.my_permissions(projectKey=project)["permissions"]
|
|
231
208
|
|
|
232
209
|
def can_i(self, permission: str) -> bool:
|
|
210
|
+
"""Return whether I have the given permission in the project."""
|
|
233
211
|
return bool(
|
|
234
212
|
self.my_permissions(project=self.project)[permission]["havePermission"]
|
|
235
213
|
)
|
|
236
214
|
|
|
237
215
|
def can_create_issues(self) -> bool:
|
|
238
|
-
|
|
216
|
+
"""Return whether I can create issues in the project."""
|
|
217
|
+
return self.can_i(CREATE_ISSUES)
|
|
239
218
|
|
|
240
219
|
def can_transition_issues(self) -> bool:
|
|
241
|
-
|
|
220
|
+
"""Return whether I can transition issues in the project."""
|
|
221
|
+
return self.can_i(TRANSITION_ISSUES)
|
|
242
222
|
|
|
243
223
|
def _project_issue_types(self, project: str) -> list[IssueType]:
|
|
224
|
+
"""Return all available issue types (e.g. Task, Bug) for the project.
|
|
225
|
+
|
|
226
|
+
Don't use this function directly, use self.project_issue_types which is cached.
|
|
227
|
+
"""
|
|
244
228
|
# Don't use self.project here, because of function.cache usage
|
|
245
229
|
return [
|
|
246
230
|
IssueType(id=t.id, name=t.name, statuses=[s.name for s in t.statuses])
|
|
@@ -248,6 +232,7 @@ class JiraClient:
|
|
|
248
232
|
]
|
|
249
233
|
|
|
250
234
|
def get_issue_type(self, issue_type: str) -> IssueType | None:
|
|
235
|
+
"""Return a issue type (e.g. Task) for the project if it exists."""
|
|
251
236
|
for _issue_type in self.project_issue_types(self.project):
|
|
252
237
|
if _issue_type.name == issue_type:
|
|
253
238
|
return _issue_type
|
|
@@ -255,15 +240,23 @@ class JiraClient:
|
|
|
255
240
|
|
|
256
241
|
@staticmethod
|
|
257
242
|
def _get_allowed_issue_field_options(
|
|
258
|
-
allowed_values: list[Resource],
|
|
243
|
+
allowed_values: list[Resource] | list[dict[str, str]],
|
|
259
244
|
) -> list[FieldOption | CustomFieldOption]:
|
|
260
|
-
"""Return a list of allowed values for a field."""
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
245
|
+
"""Return a list of allowed values for a field. E.g. Minor, Major ... for Priority in a Task."""
|
|
246
|
+
items: list[FieldOption | CustomFieldOption] = []
|
|
247
|
+
for v in allowed_values:
|
|
248
|
+
match v:
|
|
249
|
+
case dict() if "value" in v:
|
|
250
|
+
items.append(CustomFieldOption(value=v["value"]))
|
|
251
|
+
case dict() if "name" in v:
|
|
252
|
+
items.append(FieldOption(name=v["name"]))
|
|
253
|
+
case JiraCustomFieldOption():
|
|
254
|
+
items.append(CustomFieldOption(value=v.value))
|
|
255
|
+
case Resource():
|
|
256
|
+
items.append(FieldOption(name=v.name))
|
|
257
|
+
case _:
|
|
258
|
+
logging.warning(f"Unknown allowed value type: {type(v)}")
|
|
259
|
+
return items
|
|
267
260
|
|
|
268
261
|
def _project_issue_fields(
|
|
269
262
|
self, project: str, issue_type_id: str
|
|
@@ -273,6 +266,27 @@ class JiraClient:
|
|
|
273
266
|
This API endpoint needs createIssue project permissions.
|
|
274
267
|
"""
|
|
275
268
|
# Don't use self.project here, because of function.cache usage
|
|
269
|
+
if self.is_cloud:
|
|
270
|
+
metadata = self.jira.createmeta(
|
|
271
|
+
projectKeys=self.project,
|
|
272
|
+
issuetypeIds=[issue_type_id],
|
|
273
|
+
expand="projects.issuetypes.fields",
|
|
274
|
+
)
|
|
275
|
+
if not metadata["projects"] or not metadata["projects"][0]["issuetypes"]:
|
|
276
|
+
return []
|
|
277
|
+
return [
|
|
278
|
+
IssueField(
|
|
279
|
+
name=field["name"],
|
|
280
|
+
id=field_id,
|
|
281
|
+
options=self._get_allowed_issue_field_options(
|
|
282
|
+
field.get("allowedValues", [])
|
|
283
|
+
),
|
|
284
|
+
)
|
|
285
|
+
for field_id, field in metadata["projects"][0]["issuetypes"][0][
|
|
286
|
+
"fields"
|
|
287
|
+
].items()
|
|
288
|
+
]
|
|
289
|
+
|
|
276
290
|
return [
|
|
277
291
|
IssueField(
|
|
278
292
|
name=field.name,
|
|
@@ -304,6 +318,10 @@ class JiraClient:
|
|
|
304
318
|
|
|
305
319
|
def project_priority_scheme(self) -> list[str]:
|
|
306
320
|
"""Return a list of all priority IDs for the project."""
|
|
321
|
+
if self.is_cloud:
|
|
322
|
+
# Cloud does not have a way to retrieve project specific priority schemes
|
|
323
|
+
return []
|
|
324
|
+
|
|
307
325
|
scheme = self.jira.project_priority_scheme(self.project)
|
|
308
326
|
return scheme.optionIds
|
|
309
327
|
|
|
@@ -329,4 +347,5 @@ class JiraClient:
|
|
|
329
347
|
|
|
330
348
|
@property
|
|
331
349
|
def is_archived(self) -> bool:
|
|
332
|
-
|
|
350
|
+
"""Return whether the project is archived."""
|
|
351
|
+
return getattr(self.jira.project(self.project), "archived", False)
|
reconcile/utils/jjb_client.py
CHANGED
|
@@ -9,6 +9,7 @@ import tempfile
|
|
|
9
9
|
import xml.etree.ElementTree as ET
|
|
10
10
|
from collections.abc import Iterable, Mapping
|
|
11
11
|
from os import path
|
|
12
|
+
from pathlib import Path
|
|
12
13
|
from subprocess import (
|
|
13
14
|
PIPE,
|
|
14
15
|
STDOUT,
|
|
@@ -17,10 +18,9 @@ from subprocess import (
|
|
|
17
18
|
from typing import Any
|
|
18
19
|
|
|
19
20
|
import yaml
|
|
20
|
-
from jenkins_jobs.builder import JenkinsManager
|
|
21
21
|
from jenkins_jobs.errors import JenkinsJobsException
|
|
22
|
-
from jenkins_jobs.
|
|
23
|
-
from jenkins_jobs.
|
|
22
|
+
from jenkins_jobs.loader import load_files
|
|
23
|
+
from jenkins_jobs.roots import Roots
|
|
24
24
|
from sretoolbox.utils import retry
|
|
25
25
|
|
|
26
26
|
from reconcile.utils import throughput
|
|
@@ -33,6 +33,10 @@ from reconcile.utils.vcs import GITHUB_BASE_URL
|
|
|
33
33
|
JJB_INI = "[jenkins]\nurl = https://JENKINS_URL"
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
class MissingJobUrlError(Exception):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
36
40
|
class JJB:
|
|
37
41
|
"""Wrapper around Jenkins Jobs"""
|
|
38
42
|
|
|
@@ -292,13 +296,10 @@ class JJB:
|
|
|
292
296
|
|
|
293
297
|
args = ["--conf", ini_path, "test", config_path]
|
|
294
298
|
jjb = self.get_jjb(args)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
jobs, _ = parser.expandYaml(registry, jjb.options.names)
|
|
300
|
-
|
|
301
|
-
return jobs
|
|
299
|
+
roots = Roots(jjb.jjb_config)
|
|
300
|
+
load_files(jjb.jjb_config, roots, [Path(config_path)])
|
|
301
|
+
job_view_data_list = roots.generate_jobs()
|
|
302
|
+
return [job.data for job in job_view_data_list]
|
|
302
303
|
|
|
303
304
|
def get_job_webhooks_data(self) -> dict[str, list[dict[str, Any]]]:
|
|
304
305
|
job_webhooks_data: dict[str, list[dict[str, Any]]] = {}
|
|
@@ -338,7 +339,7 @@ class JJB:
|
|
|
338
339
|
job_name = job["name"]
|
|
339
340
|
try:
|
|
340
341
|
repos.add(self.get_repo_url(job))
|
|
341
|
-
except
|
|
342
|
+
except MissingJobUrlError:
|
|
342
343
|
logging.debug(f"missing github url: {job_name}")
|
|
343
344
|
return repos
|
|
344
345
|
|
|
@@ -358,7 +359,19 @@ class JJB:
|
|
|
358
359
|
|
|
359
360
|
@staticmethod
|
|
360
361
|
def get_repo_url(job: Mapping[str, Any]) -> str:
|
|
361
|
-
repo_url_raw = job
|
|
362
|
+
repo_url_raw = job.get("properties", [{}])[0].get("github", {}).get("url")
|
|
363
|
+
|
|
364
|
+
# we may be in a Github Branch Source type of job
|
|
365
|
+
if not repo_url_raw:
|
|
366
|
+
gh_org = job.get("scm", [{}])[0].get("github", {}).get("repo-owner")
|
|
367
|
+
gh_repo = job.get("scm", [{}])[0].get("github", {}).get("repo")
|
|
368
|
+
if gh_org and gh_repo:
|
|
369
|
+
repo_url_raw = f"https://github.com/{gh_org}/{gh_repo}/"
|
|
370
|
+
else:
|
|
371
|
+
raise MissingJobUrlError(
|
|
372
|
+
f"Cannot find job url for {job['display-name']}"
|
|
373
|
+
)
|
|
374
|
+
|
|
362
375
|
return repo_url_raw.strip("/").replace(".git", "")
|
|
363
376
|
|
|
364
377
|
@staticmethod
|
|
@@ -407,7 +420,7 @@ class JJB:
|
|
|
407
420
|
try:
|
|
408
421
|
if self.get_repo_url(job).lower() == repo_url.rstrip("/").lower():
|
|
409
422
|
return job
|
|
410
|
-
except
|
|
423
|
+
except MissingJobUrlError:
|
|
411
424
|
# something wrong here. ignore this job
|
|
412
425
|
pass
|
|
413
426
|
raise ValueError(f"job with {job_type=} and {repo_url=} not found")
|
|
@@ -3,7 +3,7 @@ import time
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import Protocol, TextIO
|
|
5
5
|
|
|
6
|
-
from kubernetes.client import (
|
|
6
|
+
from kubernetes.client import (
|
|
7
7
|
ApiClient,
|
|
8
8
|
V1Job,
|
|
9
9
|
V1ObjectMeta,
|
|
@@ -100,7 +100,7 @@ class K8sJobController:
|
|
|
100
100
|
"""
|
|
101
101
|
new_cache = {}
|
|
102
102
|
for item in self.oc.get_items(
|
|
103
|
-
kind="Job",
|
|
103
|
+
kind="Job.batch",
|
|
104
104
|
namespace=self.namespace,
|
|
105
105
|
):
|
|
106
106
|
openshift_resource = OpenshiftResource(
|
|
@@ -38,6 +38,8 @@ class JobValidationError(Exception):
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
JOB_GENERATION_ANNOTATION = "qontract-reconcile/job.generation"
|
|
41
|
+
MAX_JOB_NAME_LENGTH = 63
|
|
42
|
+
UNIT_OF_WORK_DIGEST_LENGTH = 10
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
class K8sJob(ABC):
|
|
@@ -72,7 +74,21 @@ class K8sJob(ABC):
|
|
|
72
74
|
"""
|
|
73
75
|
|
|
74
76
|
def name(self) -> str:
|
|
75
|
-
|
|
77
|
+
"""
|
|
78
|
+
Generate the full job name by combining the name prefix with a digest.
|
|
79
|
+
|
|
80
|
+
The name is constructed from the name_prefix (truncated to ensure total
|
|
81
|
+
length compliance) and the unit_of_work_digest. The total length is
|
|
82
|
+
limited to MAX_JOB_NAME_LENGTH (63 characters) to comply with Kubernetes
|
|
83
|
+
naming constraints.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
A unique job name in the format: {name_prefix}-{digest}
|
|
87
|
+
"""
|
|
88
|
+
prefix = self.name_prefix()[
|
|
89
|
+
: MAX_JOB_NAME_LENGTH - UNIT_OF_WORK_DIGEST_LENGTH - 1
|
|
90
|
+
]
|
|
91
|
+
return f"{prefix}-{self.unit_of_work_digest(UNIT_OF_WORK_DIGEST_LENGTH)}"
|
|
76
92
|
|
|
77
93
|
@abstractmethod
|
|
78
94
|
def name_prefix(self) -> str:
|
reconcile/utils/json.py
CHANGED
|
@@ -1,15 +1,50 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from dataclasses import asdict, is_dataclass
|
|
4
|
+
from datetime import date, datetime
|
|
5
|
+
from decimal import Decimal
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
from pydantic.main import IncEx
|
|
3
11
|
|
|
4
12
|
JSON_COMPACT_SEPARATORS = (",", ":")
|
|
5
13
|
|
|
6
14
|
|
|
15
|
+
def pydantic_encoder(obj: Any) -> Any:
|
|
16
|
+
if isinstance(obj, BaseModel):
|
|
17
|
+
return obj.model_dump()
|
|
18
|
+
|
|
19
|
+
if is_dataclass(obj):
|
|
20
|
+
return asdict(obj) # type: ignore
|
|
21
|
+
|
|
22
|
+
if isinstance(obj, (datetime, date)):
|
|
23
|
+
return obj.isoformat()
|
|
24
|
+
|
|
25
|
+
if isinstance(obj, Enum):
|
|
26
|
+
return obj.value
|
|
27
|
+
|
|
28
|
+
if isinstance(obj, Decimal):
|
|
29
|
+
return float(obj)
|
|
30
|
+
|
|
31
|
+
raise TypeError(
|
|
32
|
+
f"Object of type '{obj.__class__.__name__}' is not JSON serializable"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
7
36
|
def json_dumps(
|
|
8
37
|
data: Any,
|
|
9
38
|
*,
|
|
10
39
|
compact: bool = False,
|
|
11
40
|
indent: int | None = None,
|
|
12
41
|
cls: type[json.JSONEncoder] | None = None,
|
|
42
|
+
defaults: Callable | None = None,
|
|
43
|
+
# BaseModel dump parameters
|
|
44
|
+
by_alias: bool = True,
|
|
45
|
+
exclude_none: bool = False,
|
|
46
|
+
exclude: IncEx | None = None,
|
|
47
|
+
mode: Literal["json", "python"] = "json",
|
|
13
48
|
) -> str:
|
|
14
49
|
"""
|
|
15
50
|
Serialize `data` to a consistent JSON formatted `str` with dict keys sorted.
|
|
@@ -22,6 +57,12 @@ def json_dumps(
|
|
|
22
57
|
Returns:
|
|
23
58
|
A JSON formatted string.
|
|
24
59
|
"""
|
|
60
|
+
if isinstance(data, BaseModel):
|
|
61
|
+
data = data.model_dump(
|
|
62
|
+
mode=mode, by_alias=by_alias, exclude_none=exclude_none, exclude=exclude
|
|
63
|
+
)
|
|
64
|
+
if mode == "python":
|
|
65
|
+
defaults = pydantic_encoder
|
|
25
66
|
separators = JSON_COMPACT_SEPARATORS if compact else None
|
|
26
67
|
return json.dumps(
|
|
27
68
|
data,
|
|
@@ -29,4 +70,5 @@ def json_dumps(
|
|
|
29
70
|
separators=separators,
|
|
30
71
|
sort_keys=True,
|
|
31
72
|
cls=cls,
|
|
73
|
+
default=defaults,
|
|
32
74
|
)
|
|
@@ -55,6 +55,8 @@ def resolve_app_interface_membership_source(
|
|
|
55
55
|
|
|
56
56
|
def build_member_list(role: RoleV1) -> list[RoleMember]:
|
|
57
57
|
members: list[RoleMember] = []
|
|
58
|
-
members.extend([RoleUser(**u.
|
|
59
|
-
members.extend([
|
|
58
|
+
members.extend([RoleUser(**u.model_dump()) for u in role.users or []])
|
|
59
|
+
members.extend([
|
|
60
|
+
RoleBot(**b.model_dump()) for b in role.bots or [] if b.org_username
|
|
61
|
+
])
|
|
60
62
|
return members
|
|
@@ -7,7 +7,6 @@ from typing import (
|
|
|
7
7
|
|
|
8
8
|
from pydantic import (
|
|
9
9
|
BaseModel,
|
|
10
|
-
Extra,
|
|
11
10
|
)
|
|
12
11
|
|
|
13
12
|
from reconcile.gql_definitions.fragments.membership_source import (
|
|
@@ -23,7 +22,7 @@ class User(Protocol):
|
|
|
23
22
|
@property
|
|
24
23
|
def org_username(self) -> str: ...
|
|
25
24
|
|
|
26
|
-
def
|
|
25
|
+
def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
class Bot(Protocol):
|
|
@@ -33,7 +32,7 @@ class Bot(Protocol):
|
|
|
33
32
|
@property
|
|
34
33
|
def org_username(self) -> str | None: ...
|
|
35
34
|
|
|
36
|
-
def
|
|
35
|
+
def model_dump(self, *, by_alias: bool = False) -> dict[str, Any]: ...
|
|
37
36
|
|
|
38
37
|
|
|
39
38
|
class RoleWithMemberships(Protocol):
|
|
@@ -50,33 +49,27 @@ class RoleWithMemberships(Protocol):
|
|
|
50
49
|
def member_sources(self) -> Sequence[RoleMembershipSource] | None: ...
|
|
51
50
|
|
|
52
51
|
|
|
53
|
-
class RoleUser(BaseModel):
|
|
52
|
+
class RoleUser(BaseModel, extra="ignore"):
|
|
54
53
|
name: str
|
|
55
54
|
org_username: str
|
|
56
|
-
github_username: str | None
|
|
57
|
-
quay_username: str | None
|
|
58
|
-
pagerduty_username: str | None
|
|
59
|
-
aws_username: str | None
|
|
60
|
-
cloudflare_user: str | None
|
|
61
|
-
public_gpg_key: str | None
|
|
55
|
+
github_username: str | None = None
|
|
56
|
+
quay_username: str | None = None
|
|
57
|
+
pagerduty_username: str | None = None
|
|
58
|
+
aws_username: str | None = None
|
|
59
|
+
cloudflare_user: str | None = None
|
|
60
|
+
public_gpg_key: str | None = None
|
|
62
61
|
tag_on_cluster_updates: bool | None = False
|
|
63
62
|
tag_on_merge_requests: bool | None = False
|
|
64
63
|
|
|
65
|
-
class Config:
|
|
66
|
-
extra = Extra.ignore
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
class RoleBot(BaseModel):
|
|
65
|
+
class RoleBot(BaseModel, extra="ignore"):
|
|
70
66
|
name: str
|
|
71
|
-
description: str | None
|
|
72
|
-
org_username: str | None
|
|
73
|
-
github_username: str | None
|
|
74
|
-
gitlab_username: str | None
|
|
75
|
-
openshift_serviceaccount: str | None
|
|
76
|
-
quay_username: str | None
|
|
77
|
-
|
|
78
|
-
class Config:
|
|
79
|
-
extra = Extra.ignore
|
|
67
|
+
description: str | None = None
|
|
68
|
+
org_username: str | None = None
|
|
69
|
+
github_username: str | None = None
|
|
70
|
+
gitlab_username: str | None = None
|
|
71
|
+
openshift_serviceaccount: str | None = None
|
|
72
|
+
quay_username: str | None = None
|
|
80
73
|
|
|
81
74
|
|
|
82
75
|
RoleMember = RoleUser | RoleBot
|
|
@@ -98,8 +98,10 @@ def resolve_role_members(
|
|
|
98
98
|
members: list[RoleMember] = []
|
|
99
99
|
|
|
100
100
|
# bring in the local users and bots ...
|
|
101
|
-
members.extend(RoleUser(**u.
|
|
102
|
-
members.extend(
|
|
101
|
+
members.extend(RoleUser(**u.model_dump()) for u in r.users or [])
|
|
102
|
+
members.extend(
|
|
103
|
+
RoleBot(**b.model_dump()) for b in r.bots or [] if b.org_username
|
|
104
|
+
)
|
|
103
105
|
|
|
104
106
|
# ... and enhance with the ones from member sources
|
|
105
107
|
for ms in r.member_sources or []:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from abc import abstractmethod
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
5
|
|
|
6
6
|
from gitlab.v4.objects import ProjectMergeRequest
|
|
7
7
|
from pydantic import BaseModel
|
|
@@ -17,12 +17,12 @@ T = TypeVar("T", bound=BaseModel)
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
@dataclass
|
|
20
|
-
class OpenMergeRequest
|
|
20
|
+
class OpenMergeRequest[T: BaseModel]:
|
|
21
21
|
raw: ProjectMergeRequest
|
|
22
22
|
mr_info: T
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class MergeRequestManagerBase
|
|
25
|
+
class MergeRequestManagerBase[T: BaseModel]:
|
|
26
26
|
""" """
|
|
27
27
|
|
|
28
28
|
def __init__(self, vcs: VCS, parser: Parser, mr_label: str):
|
|
@@ -42,7 +42,7 @@ class MergeRequestManagerBase(Generic[T]):
|
|
|
42
42
|
expected_data: dict[str, Any],
|
|
43
43
|
) -> OpenMergeRequest | None:
|
|
44
44
|
for mr in self._open_mrs:
|
|
45
|
-
mr_info_dict = mr.mr_info.
|
|
45
|
+
mr_info_dict = mr.mr_info.model_dump()
|
|
46
46
|
if all(mr_info_dict.get(k) == expected_data.get(k) for k in expected_data):
|
|
47
47
|
return mr
|
|
48
48
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import re
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import TypeVar
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel
|
|
5
5
|
|
|
@@ -17,7 +17,7 @@ class ParserVersionError(Exception):
|
|
|
17
17
|
T = TypeVar("T", bound=BaseModel)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class Parser
|
|
20
|
+
class Parser[T: BaseModel]:
|
|
21
21
|
"""This class is only concerned with parsing an MR description rendered by the Renderer."""
|
|
22
22
|
|
|
23
23
|
def __init__(
|
|
@@ -60,8 +60,8 @@ class Parser(Generic[T]):
|
|
|
60
60
|
|
|
61
61
|
if self.expected_version != self._find_by_name(self.version_ref, parts[1]):
|
|
62
62
|
raise ParserVersionError("Version is outdated")
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
self.klass, self._data_from_description(parts[1]), use_defaults=False
|
|
66
|
-
)
|
|
63
|
+
data = data_default_none(
|
|
64
|
+
self.klass, self._data_from_description(parts[1]), use_defaults=False
|
|
67
65
|
)
|
|
66
|
+
assert isinstance(data, dict)
|
|
67
|
+
return self.klass(**data)
|
reconcile/utils/metrics.py
CHANGED
|
@@ -144,7 +144,7 @@ class GaugeMetric(BaseMetric):
|
|
|
144
144
|
|
|
145
145
|
@classmethod
|
|
146
146
|
def metric_family(cls) -> GaugeMetricFamily:
|
|
147
|
-
labels = [f.alias for f in cls.
|
|
147
|
+
labels = [f.alias or name for name, f in cls.model_fields.items()]
|
|
148
148
|
return GaugeMetricFamily(cls.name(), cls.__doc__ or "", labels=labels)
|
|
149
149
|
|
|
150
150
|
@classmethod
|
|
@@ -167,7 +167,7 @@ class CounterMetric(BaseMetric):
|
|
|
167
167
|
|
|
168
168
|
@classmethod
|
|
169
169
|
def metric_family(cls) -> CounterMetricFamily:
|
|
170
|
-
labels = [f.alias for f in cls.
|
|
170
|
+
labels = [f.alias or name for name, f in cls.model_fields.items()]
|
|
171
171
|
return CounterMetricFamily(cls.name(), cls.__doc__ or "", labels=labels)
|
|
172
172
|
|
|
173
173
|
@classmethod
|
|
@@ -198,7 +198,7 @@ class MetricsContainer:
|
|
|
198
198
|
"""
|
|
199
199
|
Sets the value of the given gauge metric to the given value.
|
|
200
200
|
"""
|
|
201
|
-
label_values = tuple(metric.
|
|
201
|
+
label_values = tuple(metric.model_dump(by_alias=True).values())
|
|
202
202
|
self._gauges[metric.__class__][label_values] = value
|
|
203
203
|
|
|
204
204
|
def set_info(self, metric: InfoMetric) -> None:
|
|
@@ -213,7 +213,7 @@ class MetricsContainer:
|
|
|
213
213
|
Increases the value of the given counter by the given amount.
|
|
214
214
|
"""
|
|
215
215
|
# all label values need to be strings, so lets convert them
|
|
216
|
-
label_values = tuple(str(v) for v in counter.
|
|
216
|
+
label_values = tuple(str(v) for v in counter.model_dump(by_alias=True).values())
|
|
217
217
|
current_value = self._counters[counter.__class__].get(label_values) or 0
|
|
218
218
|
self._counters[counter.__class__][label_values] = current_value + by
|
|
219
219
|
|
|
@@ -270,7 +270,7 @@ class MetricsContainer:
|
|
|
270
270
|
(
|
|
271
271
|
metric_class(**{
|
|
272
272
|
key: labels[i]
|
|
273
|
-
for i, key in enumerate(metric_class.
|
|
273
|
+
for i, key in enumerate(metric_class.model_fields.keys())
|
|
274
274
|
}),
|
|
275
275
|
value,
|
|
276
276
|
)
|