qontract-reconcile 0.10.2.dev349__py3-none-any.whl → 0.10.2.dev414__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.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/METADATA +12 -11
- {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/RECORD +356 -350
- reconcile/acs_rbac.py +2 -2
- reconcile/aus/advanced_upgrade_service.py +15 -12
- reconcile/aus/base.py +26 -27
- reconcile/aus/cluster_version_data.py +15 -5
- reconcile/aus/models.py +1 -1
- reconcile/automated_actions/config/integration.py +15 -3
- 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 +2 -2
- reconcile/aws_iam_keys.py +7 -41
- reconcile/aws_saml_idp/integration.py +12 -4
- reconcile/aws_saml_roles/integration.py +32 -25
- 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 +2 -4
- reconcile/checkpoint.py +11 -3
- reconcile/cli.py +33 -8
- 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 +9 -11
- reconcile/external_resources/factories.py +5 -12
- reconcile/external_resources/integration.py +1 -1
- reconcile/external_resources/manager.py +24 -10
- reconcile/external_resources/meta.py +0 -1
- reconcile/external_resources/metrics.py +1 -1
- reconcile/external_resources/model.py +13 -13
- reconcile/external_resources/reconciler.py +7 -4
- reconcile/external_resources/secrets_sync.py +6 -8
- reconcile/external_resources/state.py +60 -17
- 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 +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 +7 -5
- reconcile/gql_definitions/common/clusters_minimal.py +5 -5
- reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
- reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
- reconcile/gql_definitions/common/github_orgs.py +5 -5
- reconcile/gql_definitions/common/jira_settings.py +5 -5
- reconcile/gql_definitions/common/jiralert_settings.py +5 -5
- reconcile/gql_definitions/common/ldap_settings.py +5 -5
- reconcile/gql_definitions/common/namespaces.py +5 -5
- reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
- reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
- reconcile/gql_definitions/common/ocm_environments.py +5 -5
- reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
- reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
- reconcile/gql_definitions/common/pipeline_providers.py +5 -5
- reconcile/gql_definitions/common/quay_instances.py +5 -5
- reconcile/gql_definitions/common/quay_orgs.py +5 -5
- reconcile/gql_definitions/common/reserved_networks.py +5 -5
- reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
- reconcile/gql_definitions/common/saas_files.py +5 -5
- reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
- reconcile/gql_definitions/common/saasherder_settings.py +5 -5
- reconcile/gql_definitions/common/slack_workspaces.py +5 -5
- reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
- reconcile/gql_definitions/common/state_aws_account.py +5 -5
- reconcile/gql_definitions/common/users.py +5 -5
- reconcile/gql_definitions/common/users_with_paths.py +5 -5
- reconcile/gql_definitions/cost_report/app_names.py +5 -5
- reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
- reconcile/gql_definitions/cost_report/settings.py +5 -5
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
- reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
- reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
- reconcile/gql_definitions/email_sender/apps.py +5 -5
- reconcile/gql_definitions/email_sender/emails.py +5 -5
- reconcile/gql_definitions/email_sender/users.py +5 -5
- reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
- reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +89 -6
- reconcile/gql_definitions/external_resources/external_resources_settings.py +7 -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 +2137 -1053
- 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 +5 -5
- 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 +38 -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 -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 +23 -23
- reconcile/openshift_base.py +53 -2
- reconcile/openshift_cluster_bots.py +3 -2
- 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 +5 -5
- reconcile/openshift_rolebindings.py +7 -11
- 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 +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 -1
- reconcile/rhidp/common.py +3 -5
- reconcile/rhidp/sso_client/base.py +1 -1
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
- reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
- reconcile/skupper_network/integration.py +2 -2
- reconcile/slack_usergroups.py +35 -14
- reconcile/sql_query.py +1 -0
- reconcile/status_board.py +6 -6
- reconcile/statuspage/atlassian.py +7 -7
- reconcile/statuspage/integrations/maintenances.py +4 -3
- reconcile/statuspage/page.py +4 -9
- reconcile/statuspage/status.py +5 -8
- reconcile/templates/rosa-classic-cluster-creation.sh.j2 +4 -0
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +3 -0
- reconcile/templating/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +4 -3
- 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 +17 -7
- 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 +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 -54
- 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/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/gql.py +4 -7
- reconcile/utils/helm.py +2 -1
- 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 +9 -12
- reconcile/utils/jobcontroller/controller.py +1 -1
- reconcile/utils/jobcontroller/models.py +17 -1
- reconcile/utils/json.py +70 -0
- 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/base.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 +118 -97
- 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 +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/openshift_resource.py +10 -5
- 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 +1 -1
- reconcile/utils/rhcsv2_certs.py +1 -4
- 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 +25 -21
- reconcile/utils/saasherder/saasherder.py +35 -24
- reconcile/utils/slack_api.py +26 -4
- reconcile/utils/sloth.py +171 -2
- reconcile/utils/sqs_gateway.py +2 -1
- reconcile/utils/state.py +2 -1
- reconcile/utils/structs.py +1 -1
- reconcile/utils/terraform_client.py +5 -4
- reconcile/utils/terrascript_aws_client.py +171 -114
- 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 +3 -1
- tools/cli_commands/systems_and_tools.py +5 -1
- tools/qontract_cli.py +31 -18
- tools/template_validation.py +3 -1
- {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev349.dist-info → qontract_reconcile-0.10.2.dev414.dist-info}/entry_points.txt +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import hashlib
|
|
2
|
-
import json
|
|
3
2
|
from abc import (
|
|
4
3
|
ABC,
|
|
5
4
|
)
|
|
@@ -23,6 +22,7 @@ from reconcile.gql_definitions.external_resources.external_resources_namespaces
|
|
|
23
22
|
NamespaceTerraformResourceElastiCacheV1,
|
|
24
23
|
NamespaceTerraformResourceKMSV1,
|
|
25
24
|
NamespaceTerraformResourceMskV1,
|
|
25
|
+
NamespaceTerraformResourceRDSProxyV1,
|
|
26
26
|
NamespaceTerraformResourceRDSV1,
|
|
27
27
|
NamespaceV1,
|
|
28
28
|
)
|
|
@@ -37,6 +37,7 @@ from reconcile.utils.exceptions import FetchResourceError
|
|
|
37
37
|
from reconcile.utils.external_resource_spec import (
|
|
38
38
|
ExternalResourceSpec,
|
|
39
39
|
)
|
|
40
|
+
from reconcile.utils.json import json_dumps
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
class ExternalResourceOrphanedResourcesError(Exception):
|
|
@@ -88,9 +89,7 @@ class ExternalResourceKey(BaseModel, frozen=True):
|
|
|
88
89
|
)
|
|
89
90
|
|
|
90
91
|
def hash(self) -> str:
|
|
91
|
-
return hashlib.md5(
|
|
92
|
-
json.dumps(self.dict(), sort_keys=True).encode("utf-8")
|
|
93
|
-
).hexdigest()
|
|
92
|
+
return hashlib.md5(json_dumps(self.model_dump()).encode("utf-8")).hexdigest()
|
|
94
93
|
|
|
95
94
|
@property
|
|
96
95
|
def state_path(self) -> str:
|
|
@@ -104,6 +103,7 @@ SUPPORTED_RESOURCE_TYPES = (
|
|
|
104
103
|
| NamespaceTerraformResourceElastiCacheV1
|
|
105
104
|
| NamespaceTerraformResourceKMSV1
|
|
106
105
|
| NamespaceTerraformResourceCloudWatchV1
|
|
106
|
+
| NamespaceTerraformResourceRDSProxyV1
|
|
107
107
|
)
|
|
108
108
|
|
|
109
109
|
|
|
@@ -115,7 +115,9 @@ class ExternalResourcesInventory(MutableMapping):
|
|
|
115
115
|
(rp, ns)
|
|
116
116
|
for ns in namespaces
|
|
117
117
|
for rp in ns.external_resources or []
|
|
118
|
-
if isinstance(rp, SUPPORTED_RESOURCE_PROVIDERS)
|
|
118
|
+
if isinstance(rp, SUPPORTED_RESOURCE_PROVIDERS)
|
|
119
|
+
and rp.resources
|
|
120
|
+
and ns.managed_external_resources
|
|
119
121
|
]
|
|
120
122
|
|
|
121
123
|
desired_specs = [
|
|
@@ -136,17 +138,17 @@ class ExternalResourcesInventory(MutableMapping):
|
|
|
136
138
|
) -> ExternalResourceSpec:
|
|
137
139
|
spec = ExternalResourceSpec(
|
|
138
140
|
provision_provider=provider.provider,
|
|
139
|
-
provisioner=provider.provisioner.
|
|
140
|
-
resource=resource.
|
|
141
|
+
provisioner=provider.provisioner.model_dump(),
|
|
142
|
+
resource=resource.model_dump(
|
|
141
143
|
exclude={
|
|
142
144
|
FLAG_RESOURCE_MANAGED_BY_ERV2,
|
|
143
145
|
FLAG_DELETE_RESOURCE,
|
|
144
146
|
MODULE_OVERRIDES,
|
|
145
147
|
}
|
|
146
148
|
),
|
|
147
|
-
namespace=namespace.
|
|
149
|
+
namespace=namespace.model_dump(by_alias=True),
|
|
148
150
|
)
|
|
149
|
-
spec.metadata[FLAG_DELETE_RESOURCE] = resource.delete
|
|
151
|
+
spec.metadata[FLAG_DELETE_RESOURCE] = resource.delete or namespace.delete
|
|
150
152
|
spec.metadata[MODULE_OVERRIDES] = resource.module_overrides
|
|
151
153
|
return spec
|
|
152
154
|
|
|
@@ -385,7 +387,7 @@ class Reconciliation(BaseModel, frozen=True):
|
|
|
385
387
|
)
|
|
386
388
|
# linked_resources store dependants resources. They will get reconciled
|
|
387
389
|
# every time the parent resource reconciliation finishes.
|
|
388
|
-
linked_resources: frozenset[ExternalResourceKey] | None
|
|
390
|
+
linked_resources: frozenset[ExternalResourceKey] | None = None
|
|
389
391
|
|
|
390
392
|
|
|
391
393
|
class ReconcileAction(StrEnum):
|
|
@@ -438,6 +440,4 @@ class ExternalResource(BaseModel):
|
|
|
438
440
|
provision: ExternalResourceProvision
|
|
439
441
|
|
|
440
442
|
def hash(self) -> str:
|
|
441
|
-
return hashlib.
|
|
442
|
-
json.dumps(self.data, sort_keys=True).encode("utf-8")
|
|
443
|
-
).hexdigest()
|
|
443
|
+
return hashlib.sha256(json_dumps(self.data).encode("utf-8")).hexdigest()
|
|
@@ -70,10 +70,13 @@ class ReconciliationK8sJob(K8sJob, BaseModel, frozen=True):
|
|
|
70
70
|
dry_run_suffix: str = ""
|
|
71
71
|
|
|
72
72
|
def name_prefix(self) -> str:
|
|
73
|
+
identifier = (
|
|
74
|
+
f"{self.reconciliation.key.provider}-{self.reconciliation.key.identifier}"
|
|
75
|
+
)
|
|
73
76
|
if self.is_dry_run:
|
|
74
|
-
return f"er-dry-run-mr-{self.dry_run_suffix}"
|
|
77
|
+
return f"er-dry-run-mr-{self.dry_run_suffix}-{identifier}"
|
|
75
78
|
else:
|
|
76
|
-
return "er"
|
|
79
|
+
return f"er-{identifier}"
|
|
77
80
|
|
|
78
81
|
def unit_of_work_identity(self) -> Any:
|
|
79
82
|
return self.reconciliation.key
|
|
@@ -96,10 +99,10 @@ class ReconciliationK8sJob(K8sJob, BaseModel, frozen=True):
|
|
|
96
99
|
image=self.reconciliation.module_configuration.image_version,
|
|
97
100
|
image_pull_policy="Always",
|
|
98
101
|
resources=V1ResourceRequirements(
|
|
99
|
-
requests=self.reconciliation.module_configuration.resources.requests.
|
|
102
|
+
requests=self.reconciliation.module_configuration.resources.requests.model_dump(
|
|
100
103
|
exclude_none=True
|
|
101
104
|
),
|
|
102
|
-
limits=self.reconciliation.module_configuration.resources.limits.
|
|
105
|
+
limits=self.reconciliation.module_configuration.resources.limits.model_dump(
|
|
103
106
|
exclude_none=True
|
|
104
107
|
),
|
|
105
108
|
),
|
|
@@ -3,7 +3,6 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
from abc import abstractmethod
|
|
5
5
|
from collections.abc import Iterable, Mapping
|
|
6
|
-
from datetime import UTC, datetime
|
|
7
6
|
from hashlib import shake_128
|
|
8
7
|
from typing import Any
|
|
9
8
|
|
|
@@ -18,17 +17,18 @@ from reconcile.external_resources.meta import (
|
|
|
18
17
|
SECRET_ANN_PROVISION_PROVIDER,
|
|
19
18
|
SECRET_ANN_PROVISIONER,
|
|
20
19
|
SECRET_UPDATED_AT,
|
|
21
|
-
SECRET_UPDATED_AT_TIMEFORMAT,
|
|
22
20
|
)
|
|
23
21
|
from reconcile.external_resources.model import (
|
|
24
22
|
ExternalResourceKey,
|
|
25
23
|
)
|
|
26
24
|
from reconcile.openshift_base import ApplyOptions, apply_action
|
|
27
25
|
from reconcile.typed_queries.clusters_minimal import get_clusters_minimal
|
|
26
|
+
from reconcile.utils.datetime_util import to_utc_seconds_iso_format, utc_now
|
|
28
27
|
from reconcile.utils.differ import diff_mappings
|
|
29
28
|
from reconcile.utils.external_resource_spec import (
|
|
30
29
|
ExternalResourceSpec,
|
|
31
30
|
)
|
|
31
|
+
from reconcile.utils.json import json_dumps
|
|
32
32
|
from reconcile.utils.oc import (
|
|
33
33
|
OCCli,
|
|
34
34
|
)
|
|
@@ -46,8 +46,8 @@ class VaultSecret(BaseModel):
|
|
|
46
46
|
|
|
47
47
|
path: str
|
|
48
48
|
field: str
|
|
49
|
-
version: int | None
|
|
50
|
-
q_format: str | None
|
|
49
|
+
version: int | None = None
|
|
50
|
+
q_format: str | None = None
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
class SecretHelper:
|
|
@@ -154,7 +154,7 @@ class SecretsReconciler:
|
|
|
154
154
|
annotations[SECRET_ANN_PROVIDER] = spec.provider
|
|
155
155
|
annotations[SECRET_ANN_IDENTIFIER] = spec.identifier
|
|
156
156
|
annotations[SECRET_UPDATED_AT] = spec.metadata[SECRET_UPDATED_AT]
|
|
157
|
-
spec.resource["annotations"] =
|
|
157
|
+
spec.resource["annotations"] = json_dumps(annotations)
|
|
158
158
|
|
|
159
159
|
def _specs_with_secret(
|
|
160
160
|
self,
|
|
@@ -351,9 +351,7 @@ class InClusterSecretsReconciler(SecretsReconciler):
|
|
|
351
351
|
secret_name = secret["metadata"]["name"]
|
|
352
352
|
spec = secrets_map[secret_name]
|
|
353
353
|
spec.secret = self.output_secrets_formatter.format(secret["data"])
|
|
354
|
-
spec.metadata[SECRET_UPDATED_AT] =
|
|
355
|
-
SECRET_UPDATED_AT_TIMEFORMAT
|
|
356
|
-
)
|
|
354
|
+
spec.metadata[SECRET_UPDATED_AT] = to_utc_seconds_iso_format(utc_now())
|
|
357
355
|
|
|
358
356
|
def _delete_source_secret(self, spec: ExternalResourceSpec) -> None:
|
|
359
357
|
secret_name = self._get_spec_outputs_secret_name(spec)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import logging
|
|
2
3
|
from collections.abc import Mapping
|
|
3
|
-
from datetime import
|
|
4
|
+
from datetime import datetime
|
|
4
5
|
from enum import StrEnum
|
|
6
|
+
from hashlib import sha256
|
|
5
7
|
from typing import Any
|
|
6
8
|
|
|
7
9
|
from pydantic import BaseModel
|
|
@@ -16,6 +18,8 @@ from reconcile.external_resources.model import (
|
|
|
16
18
|
ResourceStatus,
|
|
17
19
|
)
|
|
18
20
|
from reconcile.utils.aws_api_typed.api import AWSApi
|
|
21
|
+
from reconcile.utils.datetime_util import to_utc_microseconds_iso_format, utc_now
|
|
22
|
+
from reconcile.utils.json import json_dumps
|
|
19
23
|
|
|
20
24
|
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
|
21
25
|
|
|
@@ -41,7 +45,7 @@ class ExternalResourceState(BaseModel):
|
|
|
41
45
|
self, reconciliation_status: ReconciliationStatus
|
|
42
46
|
) -> None:
|
|
43
47
|
if self.reconciliation_needs_state_update(reconciliation_status):
|
|
44
|
-
self.ts =
|
|
48
|
+
self.ts = utc_now()
|
|
45
49
|
self.resource_status = reconciliation_status.resource_status
|
|
46
50
|
|
|
47
51
|
def reconciliation_needs_state_update(
|
|
@@ -169,8 +173,8 @@ class DynamoDBStateAdapter:
|
|
|
169
173
|
|
|
170
174
|
def serialize(self, state: ExternalResourceState) -> dict[str, Any]:
|
|
171
175
|
return {
|
|
172
|
-
self.ER_KEY_HASH: {"S": state.key.
|
|
173
|
-
self.TIMESTAMP: {"S": state.ts
|
|
176
|
+
self.ER_KEY_HASH: {"S": state.key.state_path},
|
|
177
|
+
self.TIMESTAMP: {"S": to_utc_microseconds_iso_format(state.ts)},
|
|
174
178
|
self.RESOURCE_STATUS: {"S": state.resource_status.value},
|
|
175
179
|
self.ER_KEY: {
|
|
176
180
|
"M": {
|
|
@@ -258,24 +262,63 @@ class ExternalResourcesStateDynamoDB:
|
|
|
258
262
|
self._table = table_name
|
|
259
263
|
self.partial_resources = self._get_partial_resources()
|
|
260
264
|
|
|
265
|
+
def _new_sha256_hash(self, item: dict) -> str:
|
|
266
|
+
resource_json = item[self.adapter.RECONC]["M"][self.adapter.RECONC_INPUT]["S"]
|
|
267
|
+
resource_dict = json.loads(resource_json)
|
|
268
|
+
data = resource_dict["data"]
|
|
269
|
+
return sha256(json_dumps(data).encode("utf-8")).hexdigest()
|
|
270
|
+
|
|
261
271
|
def get_external_resource_state(
|
|
262
|
-
self,
|
|
272
|
+
self,
|
|
273
|
+
key: ExternalResourceKey,
|
|
274
|
+
enable_migration: bool = False,
|
|
263
275
|
) -> ExternalResourceState:
|
|
264
276
|
data = self.aws_api.dynamodb.boto3_client.get_item(
|
|
265
277
|
TableName=self._table,
|
|
266
278
|
ConsistentRead=True,
|
|
267
|
-
Key={self.adapter.ER_KEY_HASH: {"S": key.
|
|
279
|
+
Key={self.adapter.ER_KEY_HASH: {"S": key.state_path}},
|
|
268
280
|
)
|
|
269
|
-
|
|
281
|
+
item = data.get("Item")
|
|
282
|
+
if item:
|
|
270
283
|
return self.adapter.deserialize(data["Item"])
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
284
|
+
|
|
285
|
+
old_data = self.aws_api.dynamodb.boto3_client.get_item(
|
|
286
|
+
TableName=self._table,
|
|
287
|
+
ConsistentRead=True,
|
|
288
|
+
Key={self.adapter.ER_KEY_HASH: {"S": key.hash()}},
|
|
289
|
+
)
|
|
290
|
+
old_item = old_data.get("Item")
|
|
291
|
+
if old_item:
|
|
292
|
+
old_item[self.adapter.ER_KEY_HASH]["S"] = key.state_path
|
|
293
|
+
old_item[self.adapter.RECONC]["M"][self.adapter.RECONC_RESOURCE_HASH][
|
|
294
|
+
"S"
|
|
295
|
+
] = self._new_sha256_hash(old_item)
|
|
296
|
+
if enable_migration:
|
|
297
|
+
self.aws_api.dynamodb.boto3_client.transact_write_items(
|
|
298
|
+
TransactItems=[
|
|
299
|
+
{
|
|
300
|
+
"Put": {
|
|
301
|
+
"TableName": self._table,
|
|
302
|
+
"Item": old_item,
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"Delete": {
|
|
307
|
+
"TableName": self._table,
|
|
308
|
+
"Key": {self.adapter.ER_KEY_HASH: {"S": key.hash()}},
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
]
|
|
312
|
+
)
|
|
313
|
+
return self.adapter.deserialize(old_item)
|
|
314
|
+
|
|
315
|
+
return ExternalResourceState(
|
|
316
|
+
key=key,
|
|
317
|
+
ts=utc_now(),
|
|
318
|
+
resource_status=ResourceStatus.NOT_EXISTS,
|
|
319
|
+
reconciliation=Reconciliation(key=key),
|
|
320
|
+
reconciliation_errors=0,
|
|
321
|
+
)
|
|
279
322
|
|
|
280
323
|
def set_external_resource_state(
|
|
281
324
|
self,
|
|
@@ -288,7 +331,7 @@ class ExternalResourcesStateDynamoDB:
|
|
|
288
331
|
def del_external_resource_state(self, key: ExternalResourceKey) -> None:
|
|
289
332
|
self.aws_api.dynamodb.boto3_client.delete_item(
|
|
290
333
|
TableName=self._table,
|
|
291
|
-
Key={self.adapter.ER_KEY_HASH: {"S": key.
|
|
334
|
+
Key={self.adapter.ER_KEY_HASH: {"S": key.state_path}},
|
|
292
335
|
)
|
|
293
336
|
|
|
294
337
|
def _get_partial_resources(
|
|
@@ -330,7 +373,7 @@ class ExternalResourcesStateDynamoDB:
|
|
|
330
373
|
) -> None:
|
|
331
374
|
self.aws_api.dynamodb.boto3_client.update_item(
|
|
332
375
|
TableName=self._table,
|
|
333
|
-
Key={self.adapter.ER_KEY_HASH: {"S": key.
|
|
376
|
+
Key={self.adapter.ER_KEY_HASH: {"S": key.state_path}},
|
|
334
377
|
UpdateExpression="set resource_status=:new_value",
|
|
335
378
|
ExpressionAttributeValues={":new_value": {"S": status.value}},
|
|
336
379
|
ReturnValues="UPDATED_NEW",
|
|
@@ -58,7 +58,7 @@ class FleetLabelerIntegration(QontractReconcileIntegration[NoParams]):
|
|
|
58
58
|
"""Return the desired state for early exit."""
|
|
59
59
|
return {
|
|
60
60
|
"version": QONTRACT_INTEGRATION_VERSION,
|
|
61
|
-
"specs": {spec.name: spec.
|
|
61
|
+
"specs": {spec.name: spec.model_dump() for spec in get_fleet_label_specs()},
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
def run(self, dry_run: bool) -> None:
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import logging
|
|
3
2
|
import sys
|
|
4
3
|
from collections.abc import (
|
|
@@ -17,9 +16,11 @@ from reconcile import queries
|
|
|
17
16
|
from reconcile.status import ExitCodes
|
|
18
17
|
from reconcile.utils.aggregated_list import RunnerError
|
|
19
18
|
from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
|
|
19
|
+
from reconcile.utils.datetime_util import ensure_utc, utc_now
|
|
20
20
|
from reconcile.utils.defer import defer
|
|
21
21
|
from reconcile.utils.disabled_integrations import integration_is_enabled
|
|
22
22
|
from reconcile.utils.external_resources import get_external_resource_specs
|
|
23
|
+
from reconcile.utils.json import json_dumps
|
|
23
24
|
from reconcile.utils.openshift_resource import (
|
|
24
25
|
OpenshiftResource,
|
|
25
26
|
ResourceInventory,
|
|
@@ -39,12 +40,12 @@ def construct_gabi_oc_resource(
|
|
|
39
40
|
"kind": "ConfigMap",
|
|
40
41
|
"metadata": {"name": name, "annotations": {"qontract.recycle": "true"}},
|
|
41
42
|
"data": {
|
|
42
|
-
"config.json":
|
|
43
|
+
"config.json": json_dumps(
|
|
43
44
|
{
|
|
44
45
|
"expiration": str(expiration_date),
|
|
45
46
|
"users": users,
|
|
46
47
|
},
|
|
47
|
-
|
|
48
|
+
compact=True,
|
|
48
49
|
),
|
|
49
50
|
},
|
|
50
51
|
}
|
|
@@ -65,8 +66,10 @@ def fetch_desired_state(
|
|
|
65
66
|
gabi_instances: Iterable[Mapping], ri: ResourceInventory
|
|
66
67
|
) -> None:
|
|
67
68
|
for g in gabi_instances:
|
|
68
|
-
expiration_date =
|
|
69
|
-
|
|
69
|
+
expiration_date = ensure_utc(
|
|
70
|
+
datetime.strptime(g["expirationDate"], "%Y-%m-%d") # noqa: DTZ007
|
|
71
|
+
).date()
|
|
72
|
+
if (expiration_date - utc_now().date()).days > EXPIRATION_DAYS_MAX:
|
|
70
73
|
raise RunnerError(
|
|
71
74
|
f"The maximum expiration date of {g['name']} shall not "
|
|
72
75
|
f"exceed {EXPIRATION_DAYS_MAX} days from today"
|
reconcile/gcp_image_mirror.py
CHANGED
|
@@ -132,7 +132,7 @@ class QuayMirror:
|
|
|
132
132
|
mirror_creds = None
|
|
133
133
|
pull_credentials = item.mirror.pull_credentials
|
|
134
134
|
if pull_credentials:
|
|
135
|
-
raw_data = self.secret_reader.read_all(pull_credentials.
|
|
135
|
+
raw_data = self.secret_reader.read_all(pull_credentials.model_dump())
|
|
136
136
|
username = raw_data["user"]
|
|
137
137
|
password = raw_data["token"]
|
|
138
138
|
mirror_creds = f"{username}:{password}"
|
|
@@ -226,7 +226,7 @@ class QuayMirror:
|
|
|
226
226
|
return False
|
|
227
227
|
|
|
228
228
|
def _decode_push_secret(self, secret: VaultSecret) -> str:
|
|
229
|
-
raw_data = self.secret_reader.read_all(secret.
|
|
229
|
+
raw_data = self.secret_reader.read_all(secret.model_dump())
|
|
230
230
|
token = base64.b64decode(raw_data["token"]).decode()
|
|
231
231
|
return f"{raw_data['user']}:{token}"
|
|
232
232
|
|
reconcile/github_org.py
CHANGED
reconcile/github_owners.py
CHANGED
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
import os
|
|
3
3
|
|
|
4
4
|
import github
|
|
5
|
+
import github.NamedUser
|
|
5
6
|
from github import Github
|
|
6
7
|
from sretoolbox.utils import retry
|
|
7
8
|
|
|
@@ -100,4 +101,7 @@ def run(dry_run: bool) -> None:
|
|
|
100
101
|
|
|
101
102
|
if not dry_run:
|
|
102
103
|
gh_user = gh.get_user(github_username)
|
|
104
|
+
assert isinstance(
|
|
105
|
+
gh_user, github.NamedUser.NamedUser
|
|
106
|
+
) # make mypy happy
|
|
103
107
|
gh_org.add_to_members(gh_user, "admin")
|
reconcile/gitlab_housekeeping.py
CHANGED
|
@@ -6,7 +6,6 @@ from collections.abc import (
|
|
|
6
6
|
from contextlib import suppress
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from datetime import (
|
|
9
|
-
UTC,
|
|
10
9
|
datetime,
|
|
11
10
|
timedelta,
|
|
12
11
|
)
|
|
@@ -30,6 +29,7 @@ from sretoolbox.utils import retry
|
|
|
30
29
|
|
|
31
30
|
from reconcile import queries
|
|
32
31
|
from reconcile.change_owners.change_types import ChangeTypePriority
|
|
32
|
+
from reconcile.utils.datetime_util import ensure_utc, from_utc_iso_format, utc_now
|
|
33
33
|
from reconcile.utils.gitlab_api import (
|
|
34
34
|
GitLabApi,
|
|
35
35
|
MRState,
|
|
@@ -72,7 +72,6 @@ HOLD_LABELS = [
|
|
|
72
72
|
]
|
|
73
73
|
|
|
74
74
|
QONTRACT_INTEGRATION = "gitlab-housekeeping"
|
|
75
|
-
DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
76
75
|
EXPIRATION_DATE_FORMAT = "%Y-%m-%d"
|
|
77
76
|
SQUASH_OPTION_ALWAYS = "always"
|
|
78
77
|
|
|
@@ -128,9 +127,7 @@ def _calculate_time_since_approval(approved_at: str) -> float:
|
|
|
128
127
|
Returns the number of minutes since a MR has been approved.
|
|
129
128
|
:param approved_at: the datetime the MR was approved in format %Y-%m-%dT%H:%M:%S.%fZ
|
|
130
129
|
"""
|
|
131
|
-
time_since_approval =
|
|
132
|
-
approved_at, DATE_FORMAT
|
|
133
|
-
)
|
|
130
|
+
time_since_approval = utc_now() - from_utc_iso_format(approved_at)
|
|
134
131
|
return time_since_approval.total_seconds() / 60
|
|
135
132
|
|
|
136
133
|
|
|
@@ -138,7 +135,7 @@ def get_timed_out_pipelines(
|
|
|
138
135
|
pipelines: list[ProjectMergeRequestPipeline],
|
|
139
136
|
pipeline_timeout: int = 60,
|
|
140
137
|
) -> list[ProjectMergeRequestPipeline]:
|
|
141
|
-
now =
|
|
138
|
+
now = utc_now()
|
|
142
139
|
|
|
143
140
|
pending_pipelines = [
|
|
144
141
|
p
|
|
@@ -152,7 +149,7 @@ def get_timed_out_pipelines(
|
|
|
152
149
|
timed_out_pipelines = []
|
|
153
150
|
|
|
154
151
|
for p in pending_pipelines:
|
|
155
|
-
update_time =
|
|
152
|
+
update_time = from_utc_iso_format(p.updated_at)
|
|
156
153
|
|
|
157
154
|
elapsed = (now - update_time).total_seconds()
|
|
158
155
|
|
|
@@ -279,7 +276,7 @@ def handle_stale_items(
|
|
|
279
276
|
) -> None:
|
|
280
277
|
LABEL = "stale" # noqa: N806
|
|
281
278
|
|
|
282
|
-
now =
|
|
279
|
+
now = utc_now()
|
|
283
280
|
for item in items:
|
|
284
281
|
if AUTO_MERGE in item.labels:
|
|
285
282
|
if item.merge_status == MRStatus.UNCHECKED:
|
|
@@ -287,7 +284,7 @@ def handle_stale_items(
|
|
|
287
284
|
item = gl.get_merge_request(item.iid)
|
|
288
285
|
if item.merge_status == MRStatus.CANNOT_BE_MERGED:
|
|
289
286
|
close_item(dry_run, gl, enable_closing, item_type, item)
|
|
290
|
-
update_date =
|
|
287
|
+
update_date = from_utc_iso_format(item.updated_at)
|
|
291
288
|
|
|
292
289
|
# if item is over days_interval
|
|
293
290
|
current_interval = now.date() - update_date.date()
|
|
@@ -315,10 +312,9 @@ def handle_stale_items(
|
|
|
315
312
|
if not cancel_notes:
|
|
316
313
|
continue
|
|
317
314
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
latest_cancel_note_date = max(d for d in cancel_notes_dates)
|
|
315
|
+
latest_cancel_note_date = max(
|
|
316
|
+
from_utc_iso_format(note.updated_at) for note in cancel_notes
|
|
317
|
+
)
|
|
322
318
|
# if the latest cancel note is under
|
|
323
319
|
# days_interval - remove 'stale' label
|
|
324
320
|
current_interval = now.date() - latest_cancel_note_date.date()
|
|
@@ -653,8 +649,10 @@ def publish_access_token_expiration_metrics(gl: GitLabApi) -> None:
|
|
|
653
649
|
|
|
654
650
|
for pat in pats:
|
|
655
651
|
if pat.active:
|
|
656
|
-
expiration_date =
|
|
657
|
-
|
|
652
|
+
expiration_date = ensure_utc(
|
|
653
|
+
datetime.strptime(pat.expires_at, EXPIRATION_DATE_FORMAT) # noqa: DTZ007
|
|
654
|
+
)
|
|
655
|
+
days_until_expiration = expiration_date.date() - utc_now().date()
|
|
658
656
|
gitlab_token_expiration.labels(pat.name).set(days_until_expiration.days)
|
|
659
657
|
else:
|
|
660
658
|
with suppress(KeyError, ValueError):
|
reconcile/gitlab_members.py
CHANGED
|
@@ -44,19 +44,13 @@ class GitlabUser(BaseModel):
|
|
|
44
44
|
access_level: int
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
class CurrentStateSpec(BaseModel):
|
|
47
|
+
class CurrentStateSpec(BaseModel, arbitrary_types_allowed=True):
|
|
48
48
|
members: dict[str, GroupMember]
|
|
49
49
|
|
|
50
|
-
class Config:
|
|
51
|
-
arbitrary_types_allowed = True
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
class DesiredStateSpec(BaseModel):
|
|
51
|
+
class DesiredStateSpec(BaseModel, arbitrary_types_allowed=True):
|
|
55
52
|
members: dict[str, GitlabUser]
|
|
56
53
|
|
|
57
|
-
class Config:
|
|
58
|
-
arbitrary_types_allowed = True
|
|
59
|
-
|
|
60
54
|
|
|
61
55
|
CurrentState = dict[str, CurrentStateSpec]
|
|
62
56
|
DesiredState = dict[str, DesiredStateSpec]
|
|
@@ -122,8 +116,8 @@ def build_desired_state_spec(
|
|
|
122
116
|
pagerduty_map,
|
|
123
117
|
get_username_method=lambda u: u.org_username,
|
|
124
118
|
)
|
|
125
|
-
for
|
|
126
|
-
gu = GitlabUser(user=
|
|
119
|
+
for pu in usernames_from_pagerduty:
|
|
120
|
+
gu = GitlabUser(user=pu, access_level=p_access_level)
|
|
127
121
|
add_or_update_user(desired_state_spec, gu)
|
|
128
122
|
return desired_state_spec
|
|
129
123
|
|
|
@@ -239,6 +233,6 @@ def reconcile_gitlab_members(
|
|
|
239
233
|
def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
|
|
240
234
|
gqlapi = gql.get_api()
|
|
241
235
|
return {
|
|
242
|
-
"instance": get_gitlab_instance(gqlapi.query).
|
|
243
|
-
"permissions": [p.
|
|
236
|
+
"instance": get_gitlab_instance(gqlapi.query).model_dump(),
|
|
237
|
+
"permissions": [p.model_dump() for p in get_permissions(gqlapi.query)],
|
|
244
238
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
SQS Consumer to create Gitlab merge requests.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import json
|
|
6
5
|
import logging
|
|
7
6
|
import sys
|
|
8
7
|
from collections.abc import Callable
|
|
@@ -11,6 +10,7 @@ from reconcile import queries
|
|
|
11
10
|
from reconcile.utils import mr
|
|
12
11
|
from reconcile.utils.defer import defer
|
|
13
12
|
from reconcile.utils.gitlab_api import GitLabApi
|
|
13
|
+
from reconcile.utils.json import json_dumps
|
|
14
14
|
from reconcile.utils.secret_reader import SecretReader
|
|
15
15
|
from reconcile.utils.sqs_gateway import SQSGateway
|
|
16
16
|
|
|
@@ -51,7 +51,7 @@ def run(dry_run: str, gitlab_project_id: str, defer: Callable | None = None) ->
|
|
|
51
51
|
for m in messages:
|
|
52
52
|
receipt_handle, body = m[0], m[1]
|
|
53
53
|
logging.info(
|
|
54
|
-
"received message %s with body %s", receipt_handle[:6],
|
|
54
|
+
"received message %s with body %s", receipt_handle[:6], json_dumps(body)
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
if not dry_run:
|
reconcile/gitlab_owners.py
CHANGED
|
@@ -2,12 +2,12 @@ import logging
|
|
|
2
2
|
from collections.abc import Callable, Mapping
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from dateutil import parser as dateparser
|
|
6
5
|
from gitlab.v4.objects import ProjectMergeRequest
|
|
7
6
|
from sretoolbox.utils import threaded
|
|
8
7
|
|
|
9
8
|
from reconcile import queries
|
|
10
9
|
from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
|
|
10
|
+
from reconcile.utils.datetime_util import from_utc_iso_format
|
|
11
11
|
from reconcile.utils.defer import defer
|
|
12
12
|
from reconcile.utils.gitlab_api import (
|
|
13
13
|
GitLabApi,
|
|
@@ -49,12 +49,14 @@ class MRApproval:
|
|
|
49
49
|
self.dry_run = dry_run
|
|
50
50
|
self.persistent_lgtm = persistent_lgtm
|
|
51
51
|
|
|
52
|
-
# Get the date of the most recent commit (top commit) in the MR
|
|
53
|
-
self.top_commit_created_at =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
# Get the date of the most recent commit (top commit) in the MR
|
|
53
|
+
self.top_commit_created_at = next(
|
|
54
|
+
(
|
|
55
|
+
from_utc_iso_format(commit.created_at)
|
|
56
|
+
for commit in merge_request.commits()
|
|
57
|
+
),
|
|
58
|
+
None,
|
|
59
|
+
)
|
|
58
60
|
|
|
59
61
|
def get_change_owners_map(self) -> dict[str, dict[str, list[str]]]:
|
|
60
62
|
"""
|
|
@@ -95,9 +97,10 @@ class MRApproval:
|
|
|
95
97
|
|
|
96
98
|
# Only interested in comments created after the top commit
|
|
97
99
|
# creation time
|
|
98
|
-
comment_created_at =
|
|
100
|
+
comment_created_at = from_utc_iso_format(comment.created_at)
|
|
99
101
|
if (
|
|
100
|
-
|
|
102
|
+
self.top_commit_created_at is not None
|
|
103
|
+
and comment_created_at < self.top_commit_created_at
|
|
101
104
|
and not self.persistent_lgtm
|
|
102
105
|
):
|
|
103
106
|
continue
|
|
@@ -185,9 +188,10 @@ class MRApproval:
|
|
|
185
188
|
# If the comment was created before the last commit,
|
|
186
189
|
# it means we had a push after the comment. In this case,
|
|
187
190
|
# we delete the comment and move on.
|
|
188
|
-
comment_created_at =
|
|
191
|
+
comment_created_at = from_utc_iso_format(comment.created_at)
|
|
189
192
|
if (
|
|
190
|
-
|
|
193
|
+
self.top_commit_created_at is not None
|
|
194
|
+
and comment_created_at < self.top_commit_created_at
|
|
191
195
|
and comment.note is not None
|
|
192
196
|
):
|
|
193
197
|
# Deleting stale comments
|
reconcile/gitlab_permissions.py
CHANGED
|
@@ -94,14 +94,6 @@ class GroupPermissionHandler:
|
|
|
94
94
|
desired_state: dict[str, GroupSpec],
|
|
95
95
|
current_state: dict[str, GroupSpec],
|
|
96
96
|
) -> None:
|
|
97
|
-
# gather list of app-interface managed repos
|
|
98
|
-
instance = queries.get_gitlab_instance()
|
|
99
|
-
managed_repos = {
|
|
100
|
-
f"{instance['url']}/{project_request['group']}/{r}"
|
|
101
|
-
for project_request in instance.get("projectRequests", [])
|
|
102
|
-
for r in project_request.get("projects", [])
|
|
103
|
-
}
|
|
104
|
-
|
|
105
97
|
# get the diff data
|
|
106
98
|
diff_data = diff_mappings(
|
|
107
99
|
current=current_state,
|
|
@@ -112,11 +104,10 @@ class GroupPermissionHandler:
|
|
|
112
104
|
errors: list[Exception] = []
|
|
113
105
|
for repo in diff_data.add:
|
|
114
106
|
project = self.gl.get_project(repo)
|
|
115
|
-
if not project
|
|
116
|
-
logging.info(
|
|
117
|
-
f"New app-interface managed repository {repo} hasn't been created yet - skipping"
|
|
118
|
-
)
|
|
107
|
+
if not project:
|
|
108
|
+
logging.info(f"{repo} hasn't been created yet - skipping")
|
|
119
109
|
continue
|
|
110
|
+
|
|
120
111
|
if not self.can_share_project(project):
|
|
121
112
|
errors.append(
|
|
122
113
|
GroupAccessLevelError(
|
|
@@ -136,8 +127,13 @@ class GroupPermissionHandler:
|
|
|
136
127
|
group_id=self.group.id,
|
|
137
128
|
access_level=self.access_level,
|
|
138
129
|
)
|
|
130
|
+
|
|
139
131
|
for repo in diff_data.change:
|
|
140
132
|
project = self.gl.get_project(repo)
|
|
133
|
+
if not project:
|
|
134
|
+
logging.info(f"{repo} hasn't been created yet - skipping")
|
|
135
|
+
continue
|
|
136
|
+
|
|
141
137
|
if not self.can_share_project(project):
|
|
142
138
|
errors.append(
|
|
143
139
|
GroupAccessLevelError(
|