qontract-reconcile 0.10.2.dev361__py3-none-any.whl → 0.10.2.dev430__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/METADATA +13 -12
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/RECORD +351 -345
- 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_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/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 +0 -2
- reconcile/checkpoint.py +11 -3
- reconcile/cli.py +93 -10
- 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 +4 -7
- 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 +5 -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 +15 -5
- reconcile/gql_definitions/common/aws_vpcs.py +5 -5
- reconcile/gql_definitions/common/clusters.py +5 -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 +33 -6
- 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 +7 -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 +724 -129
- 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 +30 -6
- 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 +5 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -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 +97 -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_mirror.py +1 -1
- reconcile/queries.py +131 -0
- 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/templating/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +2 -2
- 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 +10 -1
- 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 +11 -11
- 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/expiration.py +7 -3
- 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/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 +7 -10
- 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 +246 -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/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 +134 -74
- 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
- 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.dev430.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev430.dist-info}/entry_points.txt +0 -0
reconcile/utils/gql.py
CHANGED
|
@@ -109,7 +109,7 @@ class GqlApi:
|
|
|
109
109
|
if int_name:
|
|
110
110
|
integrations = self.query(INTEGRATIONS_QUERY, skip_validation=True)
|
|
111
111
|
|
|
112
|
-
for integration in integrations["integrations"]:
|
|
112
|
+
for integration in integrations["integrations"] if integrations else []:
|
|
113
113
|
if integration["name"] == int_name:
|
|
114
114
|
self._valid_schemas = integration["schemas"]
|
|
115
115
|
break
|
|
@@ -142,7 +142,7 @@ class GqlApi:
|
|
|
142
142
|
query: str,
|
|
143
143
|
variables: dict[str, Any] | None = None,
|
|
144
144
|
skip_validation: bool = False,
|
|
145
|
-
) -> dict[str, Any]
|
|
145
|
+
) -> dict[str, Any]:
|
|
146
146
|
try:
|
|
147
147
|
result = self.client.execute(
|
|
148
148
|
gql(query), variables, get_execution_result=True
|
|
@@ -172,11 +172,8 @@ class GqlApi:
|
|
|
172
172
|
if forbidden_schemas:
|
|
173
173
|
raise GqlApiErrorForbiddenSchemaError(forbidden_schemas)
|
|
174
174
|
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
if result["data"] is None:
|
|
178
|
-
raise GqlApiError("`data` not received in GraphQL payload")
|
|
179
|
-
|
|
175
|
+
# make mypy happy
|
|
176
|
+
assert "data" in result and result["data"] is not None
|
|
180
177
|
return result["data"]
|
|
181
178
|
|
|
182
179
|
def get_template(self, path: str) -> dict[str, str]:
|
reconcile/utils/helpers.py
CHANGED
|
@@ -40,7 +40,7 @@ class InstrumentedImage(Image):
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class InstrumentedSkopeo(Skopeo):
|
|
43
|
-
def copy(self, *args: Any, **kwargs: Any) ->
|
|
43
|
+
def copy(self, *args: Any, **kwargs: Any) -> None:
|
|
44
44
|
metrics.copy_count.labels(
|
|
45
45
|
integration=INTEGRATION_NAME, shard=SHARDS, shard_id=SHARD_ID
|
|
46
46
|
).inc()
|
|
@@ -140,7 +140,7 @@ class InternalGroupsClient:
|
|
|
140
140
|
with self._api as api:
|
|
141
141
|
return Group(
|
|
142
142
|
**api.create_group(
|
|
143
|
-
data=group.
|
|
143
|
+
data=group.model_dump(by_alias=True),
|
|
144
144
|
)
|
|
145
145
|
)
|
|
146
146
|
|
|
@@ -155,6 +155,6 @@ class InternalGroupsClient:
|
|
|
155
155
|
return Group(
|
|
156
156
|
**api.update_group(
|
|
157
157
|
name=group.name,
|
|
158
|
-
data=group.
|
|
158
|
+
data=group.model_dump(by_alias=True),
|
|
159
159
|
)
|
|
160
160
|
)
|
|
@@ -27,7 +27,7 @@ class Entity(BaseModel):
|
|
|
27
27
|
return hash(self.id)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class Group(BaseModel):
|
|
30
|
+
class Group(BaseModel, validate_by_name=True, validate_by_alias=True):
|
|
31
31
|
name: str
|
|
32
32
|
description: str
|
|
33
33
|
member_approval_type: str = Field("self-service", alias="memberApprovalType")
|
|
@@ -35,16 +35,18 @@ class Group(BaseModel):
|
|
|
35
35
|
owners: list[Entity]
|
|
36
36
|
display_name: str = Field(..., alias="displayName")
|
|
37
37
|
notes: str | None = None
|
|
38
|
-
rover_group_member_query: str | None = Field(
|
|
38
|
+
rover_group_member_query: str | None = Field(
|
|
39
|
+
None, alias="roverGroupMemberQuery", exclude=True
|
|
40
|
+
)
|
|
39
41
|
rover_group_inclusions: list[Entity] | None = Field(
|
|
40
|
-
None, alias="roverGroupInclusions"
|
|
42
|
+
None, alias="roverGroupInclusions", exclude=True
|
|
41
43
|
)
|
|
42
44
|
rover_group_exclusions: list[Entity] | None = Field(
|
|
43
|
-
None, alias="roverGroupExclusions"
|
|
45
|
+
None, alias="roverGroupExclusions", exclude=True
|
|
44
46
|
)
|
|
45
47
|
members: list[Entity] = []
|
|
46
|
-
member_of: list[str] | None = Field(None, alias="memberOf")
|
|
47
|
-
namespace: str | None = None
|
|
48
|
+
member_of: list[str] | None = Field(None, alias="memberOf", exclude=True)
|
|
49
|
+
namespace: str | None = Field(None, exclude=True)
|
|
48
50
|
|
|
49
51
|
def __eq__(self, other: object) -> bool:
|
|
50
52
|
if not isinstance(other, Group):
|
|
@@ -58,14 +60,3 @@ class Group(BaseModel):
|
|
|
58
60
|
and self.notes == other.notes
|
|
59
61
|
and set(self.members) == set(other.members)
|
|
60
62
|
)
|
|
61
|
-
|
|
62
|
-
class Config:
|
|
63
|
-
allow_population_by_field_name = True
|
|
64
|
-
# exclude read-only fields in the json/dict dumps
|
|
65
|
-
fields = {
|
|
66
|
-
"rover_group_member_query": {"exclude": True},
|
|
67
|
-
"rover_group_inclusions": {"exclude": True},
|
|
68
|
-
"rover_group_exclusions": {"exclude": True},
|
|
69
|
-
"member_of": {"exclude": True},
|
|
70
|
-
"namespace": {"exclude": True},
|
|
71
|
-
}
|
reconcile/utils/jinja2/utils.py
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import os
|
|
3
|
-
import subprocess
|
|
4
|
-
import tempfile
|
|
5
3
|
from collections.abc import Mapping
|
|
6
4
|
from functools import cache
|
|
7
5
|
from typing import Any, Self
|
|
8
6
|
|
|
9
7
|
import jinja2
|
|
10
|
-
import yaml
|
|
11
8
|
from github import Github
|
|
12
9
|
from jinja2.sandbox import SandboxedEnvironment
|
|
13
10
|
from pydantic import BaseModel
|
|
@@ -18,6 +15,7 @@ from reconcile.checkpoint import url_makes_sense
|
|
|
18
15
|
from reconcile.github_org import get_default_config
|
|
19
16
|
from reconcile.utils import gql
|
|
20
17
|
from reconcile.utils.aws_api import AWSApi
|
|
18
|
+
from reconcile.utils.datetime_util import utc_now
|
|
21
19
|
from reconcile.utils.github_api import GithubRepositoryApi
|
|
22
20
|
from reconcile.utils.helpers import flatten
|
|
23
21
|
from reconcile.utils.jinja2.extensions import B64EncodeExtension, RaiseErrorExtension
|
|
@@ -38,7 +36,7 @@ from reconcile.utils.secret_reader import (
|
|
|
38
36
|
SecretReader,
|
|
39
37
|
SecretReaderBase,
|
|
40
38
|
)
|
|
41
|
-
from reconcile.utils.sloth import
|
|
39
|
+
from reconcile.utils.sloth import generate_sloth_rules
|
|
42
40
|
from reconcile.utils.vault import SecretFieldNotFoundError
|
|
43
41
|
|
|
44
42
|
|
|
@@ -47,14 +45,11 @@ class Jinja2TemplateError(Exception):
|
|
|
47
45
|
super().__init__("error processing jinja2 template: " + str(msg))
|
|
48
46
|
|
|
49
47
|
|
|
50
|
-
class TemplateRenderOptions(BaseModel):
|
|
48
|
+
class TemplateRenderOptions(BaseModel, frozen=True):
|
|
51
49
|
trim_blocks: bool
|
|
52
50
|
lstrip_blocks: bool
|
|
53
51
|
keep_trailing_newline: bool
|
|
54
52
|
|
|
55
|
-
class Config:
|
|
56
|
-
frozen = True
|
|
57
|
-
|
|
58
53
|
@classmethod
|
|
59
54
|
def create(
|
|
60
55
|
cls,
|
|
@@ -77,7 +72,7 @@ def compile_jinja2_template(
|
|
|
77
72
|
) -> Any:
|
|
78
73
|
if not template_render_options:
|
|
79
74
|
template_render_options = TemplateRenderOptions.create()
|
|
80
|
-
env: dict[str, Any] = template_render_options.
|
|
75
|
+
env: dict[str, Any] = template_render_options.model_dump()
|
|
81
76
|
if extra_curly:
|
|
82
77
|
env.update({
|
|
83
78
|
"block_start_string": "{{%",
|
|
@@ -192,89 +187,6 @@ def list_s3_objects(
|
|
|
192
187
|
)
|
|
193
188
|
|
|
194
189
|
|
|
195
|
-
def sloth_alerts(
|
|
196
|
-
service: str,
|
|
197
|
-
slo_name: str,
|
|
198
|
-
objective: float,
|
|
199
|
-
error_query: str,
|
|
200
|
-
total_query: str,
|
|
201
|
-
version: str = "prometheus/v1",
|
|
202
|
-
) -> str:
|
|
203
|
-
"""Generate Prometheus rules using sloth: https://sloth.dev
|
|
204
|
-
|
|
205
|
-
Args:
|
|
206
|
-
service: Service name identifier
|
|
207
|
-
slo_name: Name of the SLO
|
|
208
|
-
objective: Target percentage (e.g. 99.9)
|
|
209
|
-
error_query: Prometheus query for error events
|
|
210
|
-
total_query: Prometheus query for total events
|
|
211
|
-
version: Spec version (default: "prometheus/v1")
|
|
212
|
-
|
|
213
|
-
Returns:
|
|
214
|
-
Generated Prometheus rules as YAML string
|
|
215
|
-
"""
|
|
216
|
-
# Build the SLO definition
|
|
217
|
-
slo = {
|
|
218
|
-
"name": slo_name,
|
|
219
|
-
"objective": objective,
|
|
220
|
-
"description": f"{slo_name} SLO for {service}",
|
|
221
|
-
"sli": {
|
|
222
|
-
"events": {
|
|
223
|
-
"error_query": error_query.replace("{{window}}", "{{.window}}"),
|
|
224
|
-
"total_query": total_query.replace("{{window}}", "{{.window}}"),
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
"alerting": {
|
|
228
|
-
"name": f"{service.title()}{slo_name.title()}",
|
|
229
|
-
"annotations": {
|
|
230
|
-
"summary": f"High error rate on '{service}' {slo_name}",
|
|
231
|
-
"message": f"High error rate on '{service}' {slo_name}",
|
|
232
|
-
},
|
|
233
|
-
"page_alert": {
|
|
234
|
-
"labels": {
|
|
235
|
-
"severity": "critical",
|
|
236
|
-
"service": service,
|
|
237
|
-
"slo": slo_name,
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
"ticket_alert": {
|
|
241
|
-
"labels": {
|
|
242
|
-
"severity": "medium",
|
|
243
|
-
"service": service,
|
|
244
|
-
"slo": slo_name,
|
|
245
|
-
}
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
spec = {
|
|
251
|
-
"version": version,
|
|
252
|
-
"service": service,
|
|
253
|
-
"slos": [slo],
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
with (
|
|
257
|
-
tempfile.NamedTemporaryFile(
|
|
258
|
-
encoding="utf-8", mode="w", suffix=".yml"
|
|
259
|
-
) as input_file,
|
|
260
|
-
tempfile.NamedTemporaryFile(
|
|
261
|
-
encoding="utf-8", mode="w", suffix=".yml"
|
|
262
|
-
) as output_file,
|
|
263
|
-
):
|
|
264
|
-
yaml.dump(spec, input_file, allow_unicode=True)
|
|
265
|
-
cmd = ["sloth", "generate", "-i", input_file.name, "-o", output_file.name]
|
|
266
|
-
try:
|
|
267
|
-
subprocess.run(cmd, capture_output=True, check=True, text=True)
|
|
268
|
-
except subprocess.CalledProcessError as e:
|
|
269
|
-
error_msg = f"{e}"
|
|
270
|
-
if e.stdout:
|
|
271
|
-
error_msg += f"\nstdout: {e.stdout}"
|
|
272
|
-
if e.stderr:
|
|
273
|
-
error_msg += f"\nstderr: {e.stderr}"
|
|
274
|
-
raise SlothGenerateError(error_msg) from e
|
|
275
|
-
return process_sloth_output(output_file.name)
|
|
276
|
-
|
|
277
|
-
|
|
278
190
|
@retry()
|
|
279
191
|
def lookup_secret(
|
|
280
192
|
path: str,
|
|
@@ -345,10 +257,8 @@ def process_jinja2_template(
|
|
|
345
257
|
"s3": lookup_s3_object,
|
|
346
258
|
"s3_ls": list_s3_objects,
|
|
347
259
|
"flatten_dict": flatten,
|
|
348
|
-
"yesterday": lambda: (
|
|
349
|
-
|
|
350
|
-
),
|
|
351
|
-
"sloth_alerts": sloth_alerts,
|
|
260
|
+
"yesterday": lambda: (utc_now() - datetime.timedelta(1)).strftime("%Y-%m-%d"),
|
|
261
|
+
"sloth_alerts": generate_sloth_rules,
|
|
352
262
|
})
|
|
353
263
|
if "_template_mocks" in vars:
|
|
354
264
|
for k, v in vars["_template_mocks"].items():
|
|
@@ -381,11 +291,6 @@ def process_extracurlyjinja2_template(
|
|
|
381
291
|
)
|
|
382
292
|
|
|
383
293
|
|
|
384
|
-
class SlothGenerateError(Exception):
|
|
385
|
-
def __init__(self, msg: Any):
|
|
386
|
-
super().__init__("sloth generate failed: " + str(msg))
|
|
387
|
-
|
|
388
|
-
|
|
389
294
|
class FetchSecretError(Exception):
|
|
390
295
|
def __init__(self, msg: Any):
|
|
391
296
|
super().__init__("error fetching secret: " + str(msg))
|
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
|
|
@@ -292,13 +292,10 @@ class JJB:
|
|
|
292
292
|
|
|
293
293
|
args = ["--conf", ini_path, "test", config_path]
|
|
294
294
|
jjb = self.get_jjb(args)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
jobs, _ = parser.expandYaml(registry, jjb.options.names)
|
|
300
|
-
|
|
301
|
-
return jobs
|
|
295
|
+
roots = Roots(jjb.jjb_config)
|
|
296
|
+
load_files(jjb.jjb_config, roots, [Path(config_path)])
|
|
297
|
+
job_view_data_list = roots.generate_jobs()
|
|
298
|
+
return [job.data for job in job_view_data_list]
|
|
302
299
|
|
|
303
300
|
def get_job_webhooks_data(self) -> dict[str, list[dict[str, Any]]]:
|
|
304
301
|
job_webhooks_data: dict[str, list[dict[str, Any]]] = {}
|
|
@@ -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:
|