qontract-reconcile 0.10.2.dev310__py3-none-any.whl → 0.10.2.dev439__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.
Potentially problematic release.
This version of qontract-reconcile might be problematic. Click here for more details.
- {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/METADATA +13 -12
- {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/RECORD +396 -391
- 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 +5 -5
- 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 +10 -14
- 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 +22 -9
- 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 +494 -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 +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 +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 +3510 -1865
- 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 +450 -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 +3 -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 +5 -1
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +4 -1
- 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 +28 -20
- reconcile/terraform_users.py +27 -22
- reconcile/terraform_vpc_peerings.py +15 -3
- reconcile/terraform_vpc_resources/integration.py +23 -8
- 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 +78 -46
- 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 +18 -21
- 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 +1 -1
- reconcile/utils/terraform_client.py +29 -26
- reconcile/utils/terrascript_aws_client.py +200 -116
- reconcile/utils/three_way_diff_strategy.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
- 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/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.dev310.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev310.dist-info → qontract_reconcile-0.10.2.dev439.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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import difflib
|
|
2
2
|
import filecmp
|
|
3
|
-
import json
|
|
4
3
|
import logging
|
|
5
4
|
import os
|
|
6
5
|
import re
|
|
@@ -8,7 +7,9 @@ import shutil
|
|
|
8
7
|
import subprocess
|
|
9
8
|
import tempfile
|
|
10
9
|
import xml.etree.ElementTree as ET
|
|
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,30 +18,44 @@ 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
|
|
27
27
|
from reconcile.utils.helpers import toggle_logger
|
|
28
|
+
from reconcile.utils.json import json_dumps
|
|
29
|
+
from reconcile.utils.secret_reader import SecretReaderBase
|
|
30
|
+
from reconcile.utils.state import State
|
|
28
31
|
from reconcile.utils.vcs import GITHUB_BASE_URL
|
|
29
32
|
|
|
30
33
|
JJB_INI = "[jenkins]\nurl = https://JENKINS_URL"
|
|
31
34
|
|
|
32
35
|
|
|
36
|
+
class MissingJobUrlError(Exception):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
33
40
|
class JJB:
|
|
34
41
|
"""Wrapper around Jenkins Jobs"""
|
|
35
42
|
|
|
36
|
-
def __init__(
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
configs: list[dict[str, Any]],
|
|
46
|
+
ssl_verify: bool = True,
|
|
47
|
+
secret_reader: SecretReaderBase | None = None,
|
|
48
|
+
print_only: bool = False,
|
|
49
|
+
) -> None:
|
|
37
50
|
self.print_only = print_only
|
|
38
51
|
self.secret_reader = secret_reader
|
|
52
|
+
if not self.print_only and self.secret_reader is None:
|
|
53
|
+
raise ValueError("secret_reader must be provided if print_only is False")
|
|
39
54
|
self.collect_configs(configs)
|
|
40
55
|
self.modify_logger()
|
|
41
56
|
self.python_https_verify = str(int(ssl_verify))
|
|
42
57
|
|
|
43
|
-
def collect_configs(self, configs):
|
|
58
|
+
def collect_configs(self, configs: list[dict[str, Any]]) -> None:
|
|
44
59
|
instances = {
|
|
45
60
|
c["instance"]["name"]: {
|
|
46
61
|
"serverUrl": c["instance"]["serverUrl"],
|
|
@@ -57,7 +72,7 @@ class JJB:
|
|
|
57
72
|
server_url = data["serverUrl"]
|
|
58
73
|
wd = tempfile.mkdtemp()
|
|
59
74
|
ini = JJB_INI
|
|
60
|
-
if not self.print_only:
|
|
75
|
+
if not self.print_only and self.secret_reader:
|
|
61
76
|
ini = self.secret_reader.read(token)
|
|
62
77
|
ini = ini.replace('"', "")
|
|
63
78
|
ini = ini.replace("false", "False")
|
|
@@ -92,7 +107,7 @@ class JJB:
|
|
|
92
107
|
self.instance_urls = instance_urls
|
|
93
108
|
self.working_dirs = working_dirs
|
|
94
109
|
|
|
95
|
-
def overwrite_configs(self, configs):
|
|
110
|
+
def overwrite_configs(self, configs: Mapping[str, str] | State) -> None:
|
|
96
111
|
"""This function will override the existing
|
|
97
112
|
config files in the working directories with
|
|
98
113
|
the supplied configs"""
|
|
@@ -101,12 +116,12 @@ class JJB:
|
|
|
101
116
|
with open(config_path, "w", encoding="locale") as f:
|
|
102
117
|
f.write(configs[name])
|
|
103
118
|
|
|
104
|
-
def sort(self, configs):
|
|
119
|
+
def sort(self, configs: list[dict[str, Any]]) -> None:
|
|
105
120
|
configs.sort(key=self.sort_by_name)
|
|
106
|
-
configs.sort(key=self.sort_by_type)
|
|
121
|
+
configs.sort(key=lambda x: self.sort_by_type(x) or 0)
|
|
107
122
|
|
|
108
123
|
@staticmethod
|
|
109
|
-
def sort_by_type(config):
|
|
124
|
+
def sort_by_type(config: Mapping[str, Any]) -> int:
|
|
110
125
|
if config["type"] == "defaults":
|
|
111
126
|
return 0
|
|
112
127
|
if config["type"] == "global-defaults":
|
|
@@ -123,12 +138,13 @@ class JJB:
|
|
|
123
138
|
return 40
|
|
124
139
|
if config["type"] == "jobs":
|
|
125
140
|
return 50
|
|
141
|
+
return 100
|
|
126
142
|
|
|
127
143
|
@staticmethod
|
|
128
|
-
def sort_by_name(config):
|
|
144
|
+
def sort_by_name(config: Mapping[str, Any]) -> str:
|
|
129
145
|
return config["name"]
|
|
130
146
|
|
|
131
|
-
def get_configs(self):
|
|
147
|
+
def get_configs(self) -> dict[str, str]:
|
|
132
148
|
"""This function gets the configs from the
|
|
133
149
|
working directories"""
|
|
134
150
|
configs = {}
|
|
@@ -139,7 +155,7 @@ class JJB:
|
|
|
139
155
|
|
|
140
156
|
return configs
|
|
141
157
|
|
|
142
|
-
def generate(self, io_dir, fetch_state):
|
|
158
|
+
def generate(self, io_dir: str, fetch_state: str) -> None:
|
|
143
159
|
"""
|
|
144
160
|
Generates job definitions from JJB configs
|
|
145
161
|
|
|
@@ -163,7 +179,7 @@ class JJB:
|
|
|
163
179
|
self.execute(args)
|
|
164
180
|
throughput.change_files_ownership(io_dir)
|
|
165
181
|
|
|
166
|
-
def print_diffs(self, io_dir, instance_name=None):
|
|
182
|
+
def print_diffs(self, io_dir: str, instance_name: str | None = None) -> None:
|
|
167
183
|
"""Print the diffs between the current and
|
|
168
184
|
the desired job definitions"""
|
|
169
185
|
current_path = path.join(io_dir, "jjb", "current")
|
|
@@ -179,7 +195,7 @@ class JJB:
|
|
|
179
195
|
self.print_diff(delete, current_path, "delete")
|
|
180
196
|
self.print_diff(common, desired_path, "update")
|
|
181
197
|
|
|
182
|
-
def print_diff(self, files, replace_path, action):
|
|
198
|
+
def print_diff(self, files: Iterable[str], replace_path: str, action: str) -> None:
|
|
183
199
|
for f in files:
|
|
184
200
|
if action == "update":
|
|
185
201
|
ft = self.toggle_cd(f)
|
|
@@ -210,11 +226,16 @@ class JJB:
|
|
|
210
226
|
]
|
|
211
227
|
logging.debug("DIFF:\n" + "".join(diff))
|
|
212
228
|
|
|
213
|
-
def compare_files(
|
|
229
|
+
def compare_files(
|
|
230
|
+
self,
|
|
231
|
+
from_files: Iterable[str],
|
|
232
|
+
subtract_files: Iterable[str],
|
|
233
|
+
in_op: bool = False,
|
|
234
|
+
) -> list[str]:
|
|
214
235
|
return [f for f in from_files if (self.toggle_cd(f) in subtract_files) is in_op]
|
|
215
236
|
|
|
216
237
|
@staticmethod
|
|
217
|
-
def get_files(search_path, instance_name=None):
|
|
238
|
+
def get_files(search_path: str, instance_name: str | None = None) -> list[str]:
|
|
218
239
|
if instance_name is not None:
|
|
219
240
|
search_path = path.join(search_path, instance_name)
|
|
220
241
|
return [
|
|
@@ -222,7 +243,7 @@ class JJB:
|
|
|
222
243
|
]
|
|
223
244
|
|
|
224
245
|
@staticmethod
|
|
225
|
-
def toggle_cd(file_name):
|
|
246
|
+
def toggle_cd(file_name: str) -> str:
|
|
226
247
|
if "desired" in file_name:
|
|
227
248
|
return file_name.replace("desired", "current")
|
|
228
249
|
return file_name.replace("current", "desired")
|
|
@@ -248,43 +269,40 @@ class JJB:
|
|
|
248
269
|
raise
|
|
249
270
|
|
|
250
271
|
@staticmethod
|
|
251
|
-
def get_jjb(args):
|
|
272
|
+
def get_jjb(args: Iterable[str]) -> Any:
|
|
252
273
|
from jenkins_jobs.cli.entry import JenkinsJobs # noqa: PLC0415
|
|
253
274
|
|
|
254
275
|
return JenkinsJobs(args)
|
|
255
276
|
|
|
256
|
-
def execute(self, args):
|
|
277
|
+
def execute(self, args: Iterable[str]) -> None:
|
|
257
278
|
jjb = self.get_jjb(args)
|
|
258
279
|
with toggle_logger():
|
|
259
280
|
jjb.execute()
|
|
260
281
|
|
|
261
|
-
def modify_logger(self):
|
|
282
|
+
def modify_logger(self) -> None:
|
|
262
283
|
yaml.warnings({"YAMLLoadWarning": False})
|
|
263
284
|
formatter = logging.Formatter("%(levelname)s: %(message)s")
|
|
264
285
|
logger = logging.getLogger()
|
|
265
286
|
logger.handlers[0].setFormatter(formatter)
|
|
266
287
|
|
|
267
|
-
def cleanup(self):
|
|
288
|
+
def cleanup(self) -> None:
|
|
268
289
|
for wd in self.working_dirs.values():
|
|
269
290
|
shutil.rmtree(wd)
|
|
270
291
|
|
|
271
292
|
@retry(exceptions=(JenkinsJobsException))
|
|
272
|
-
def get_jobs(self, wd, name):
|
|
293
|
+
def get_jobs(self, wd: str, name: str) -> list[dict[str, Any]]:
|
|
273
294
|
ini_path = f"{wd}/{name}.ini"
|
|
274
295
|
config_path = f"{wd}/config.yaml"
|
|
275
296
|
|
|
276
297
|
args = ["--conf", ini_path, "test", config_path]
|
|
277
298
|
jjb = self.get_jjb(args)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
jobs, _ = parser.expandYaml(registry, jjb.options.names)
|
|
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]
|
|
283
303
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def get_job_webhooks_data(self):
|
|
287
|
-
job_webhooks_data = {}
|
|
304
|
+
def get_job_webhooks_data(self) -> dict[str, list[dict[str, Any]]]:
|
|
305
|
+
job_webhooks_data: dict[str, list[dict[str, Any]]] = {}
|
|
288
306
|
for name, wd in self.working_dirs.items():
|
|
289
307
|
jobs = self.get_jobs(wd, name)
|
|
290
308
|
|
|
@@ -313,7 +331,7 @@ class JJB:
|
|
|
313
331
|
|
|
314
332
|
return job_webhooks_data
|
|
315
333
|
|
|
316
|
-
def get_repos(self):
|
|
334
|
+
def get_repos(self) -> set[str]:
|
|
317
335
|
repos = set()
|
|
318
336
|
for name, wd in self.working_dirs.items():
|
|
319
337
|
jobs = self.get_jobs(wd, name)
|
|
@@ -321,11 +339,11 @@ class JJB:
|
|
|
321
339
|
job_name = job["name"]
|
|
322
340
|
try:
|
|
323
341
|
repos.add(self.get_repo_url(job))
|
|
324
|
-
except
|
|
342
|
+
except MissingJobUrlError:
|
|
325
343
|
logging.debug(f"missing github url: {job_name}")
|
|
326
344
|
return repos
|
|
327
345
|
|
|
328
|
-
def get_admins(self):
|
|
346
|
+
def get_admins(self) -> set[str]:
|
|
329
347
|
admins = set()
|
|
330
348
|
for name, wd in self.working_dirs.items():
|
|
331
349
|
jobs = self.get_jobs(wd, name)
|
|
@@ -340,15 +358,29 @@ class JJB:
|
|
|
340
358
|
return admins
|
|
341
359
|
|
|
342
360
|
@staticmethod
|
|
343
|
-
def get_repo_url(job):
|
|
344
|
-
repo_url_raw = job
|
|
361
|
+
def get_repo_url(job: Mapping[str, Any]) -> str:
|
|
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
|
+
|
|
345
375
|
return repo_url_raw.strip("/").replace(".git", "")
|
|
346
376
|
|
|
347
377
|
@staticmethod
|
|
348
|
-
def get_ref(job:
|
|
378
|
+
def get_ref(job: Mapping[str, Any]) -> str:
|
|
349
379
|
return job["scm"][0]["git"]["branches"][0]
|
|
350
380
|
|
|
351
|
-
def get_all_jobs(
|
|
381
|
+
def get_all_jobs(
|
|
382
|
+
self, job_types: Iterable[str] | None = None, instance_name: str | None = None
|
|
383
|
+
) -> dict[str, list[dict[str, Any]]]:
|
|
352
384
|
if job_types is None:
|
|
353
385
|
job_types = []
|
|
354
386
|
all_jobs: dict[str, list[dict]] = {}
|
|
@@ -366,8 +398,8 @@ class JJB:
|
|
|
366
398
|
|
|
367
399
|
return all_jobs
|
|
368
400
|
|
|
369
|
-
def print_jobs(self, job_name=None):
|
|
370
|
-
all_jobs = {}
|
|
401
|
+
def print_jobs(self, job_name: str | None = None) -> None:
|
|
402
|
+
all_jobs: dict[str, list[dict[str, Any]]] = {}
|
|
371
403
|
found = False
|
|
372
404
|
for name, wd in self.working_dirs.items():
|
|
373
405
|
logging.debug(f"getting jobs from {name}")
|
|
@@ -380,7 +412,7 @@ class JJB:
|
|
|
380
412
|
found = True
|
|
381
413
|
if not found:
|
|
382
414
|
raise ValueError(f"job name {job_name} is not found")
|
|
383
|
-
print(
|
|
415
|
+
print(json_dumps(all_jobs, indent=2))
|
|
384
416
|
|
|
385
417
|
def get_job_by_repo_url(self, repo_url: str, job_type: str) -> dict[str, Any]:
|
|
386
418
|
for jobs in self.get_all_jobs(job_types=[job_type]).values():
|
|
@@ -388,13 +420,13 @@ class JJB:
|
|
|
388
420
|
try:
|
|
389
421
|
if self.get_repo_url(job).lower() == repo_url.rstrip("/").lower():
|
|
390
422
|
return job
|
|
391
|
-
except
|
|
423
|
+
except MissingJobUrlError:
|
|
392
424
|
# something wrong here. ignore this job
|
|
393
425
|
pass
|
|
394
426
|
raise ValueError(f"job with {job_type=} and {repo_url=} not found")
|
|
395
427
|
|
|
396
428
|
@staticmethod
|
|
397
|
-
def get_trigger_phrases_regex(job:
|
|
429
|
+
def get_trigger_phrases_regex(job: Mapping[str, Any]) -> str | None:
|
|
398
430
|
for trigger in job["triggers"]:
|
|
399
431
|
if "gitlab" in trigger:
|
|
400
432
|
return trigger["gitlab"].get("note-regex")
|
|
@@ -403,7 +435,7 @@ class JJB:
|
|
|
403
435
|
return None
|
|
404
436
|
|
|
405
437
|
@staticmethod
|
|
406
|
-
def get_gitlab_webhook_trigger(job:
|
|
438
|
+
def get_gitlab_webhook_trigger(job: Mapping[str, Any]) -> list[str]:
|
|
407
439
|
gitlab_triggers = job["triggers"][0]["gitlab"]
|
|
408
440
|
# pr-check job should be triggered by merge request events
|
|
409
441
|
# and certain comments: [test]|/retest|/lgtm|/lgtm cancel|/hold|/hold cancel
|
|
@@ -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:
|