qontract-reconcile 0.10.2.dev361__py3-none-any.whl → 0.10.2.dev474__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/METADATA +14 -13
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/RECORD +371 -364
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/WHEEL +1 -1
- reconcile/acs_rbac.py +2 -2
- reconcile/aus/advanced_upgrade_service.py +18 -12
- reconcile/aus/aus_sts_gate_handler.py +59 -0
- reconcile/aus/base.py +137 -34
- reconcile/aus/cluster_version_data.py +15 -5
- reconcile/aus/models.py +3 -1
- reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
- reconcile/aus/ocm_upgrade_scheduler.py +8 -1
- reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
- reconcile/aus/version_gate_approver.py +1 -16
- reconcile/aus/version_gates/sts_version_gate_handler.py +5 -72
- reconcile/automated_actions/config/integration.py +16 -4
- reconcile/aws_account_manager/integration.py +21 -9
- reconcile/aws_account_manager/reconciler.py +3 -3
- reconcile/aws_account_manager/utils.py +1 -1
- reconcile/aws_ami_cleanup/integration.py +8 -12
- reconcile/aws_ami_share.py +69 -62
- reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
- reconcile/aws_ecr_image_pull_secrets.py +1 -1
- reconcile/aws_iam_keys.py +1 -0
- reconcile/aws_saml_idp/integration.py +12 -4
- reconcile/aws_saml_roles/integration.py +30 -23
- reconcile/aws_version_sync/integration.py +6 -12
- reconcile/change_owners/README.md +1 -1
- reconcile/change_owners/bundle.py +3 -3
- reconcile/change_owners/change_log_tracking.py +3 -2
- reconcile/change_owners/change_owners.py +108 -42
- reconcile/change_owners/decision.py +1 -1
- reconcile/change_owners/diff.py +0 -2
- reconcile/checkpoint.py +11 -3
- reconcile/cli.py +94 -11
- reconcile/dashdotdb_dora.py +5 -12
- reconcile/dashdotdb_slo.py +1 -1
- reconcile/database_access_manager.py +123 -117
- reconcile/dynatrace_token_provider/integration.py +1 -1
- reconcile/endpoints_discovery/integration.py +4 -1
- reconcile/endpoints_discovery/merge_request.py +1 -1
- reconcile/endpoints_discovery/merge_request_manager.py +8 -8
- reconcile/external_resources/factories.py +4 -6
- reconcile/external_resources/integration.py +1 -1
- reconcile/external_resources/manager.py +8 -6
- reconcile/external_resources/meta.py +0 -1
- reconcile/external_resources/metrics.py +1 -1
- reconcile/external_resources/model.py +19 -15
- reconcile/external_resources/reconciler.py +7 -4
- reconcile/external_resources/secrets_sync.py +6 -10
- reconcile/external_resources/state.py +26 -16
- reconcile/fleet_labeler/integration.py +1 -1
- reconcile/gabi_authorized_users.py +5 -2
- reconcile/gcp_image_mirror.py +2 -2
- reconcile/github_org.py +1 -1
- reconcile/github_owners.py +4 -0
- reconcile/gitlab_housekeeping.py +13 -15
- reconcile/gitlab_members.py +6 -12
- reconcile/gitlab_owners.py +15 -11
- reconcile/gitlab_permissions.py +8 -12
- reconcile/glitchtip_project_alerts/integration.py +3 -1
- reconcile/gql_definitions/acs/acs_instances.py +5 -5
- reconcile/gql_definitions/acs/acs_policies.py +5 -5
- reconcile/gql_definitions/acs/acs_rbac.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +5 -5
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +5 -5
- reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
- reconcile/gql_definitions/automated_actions/instance.py +46 -7
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +14 -5
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +27 -66
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +15 -5
- reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
- reconcile/gql_definitions/aws_version_sync/clusters.py +5 -5
- reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
- reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
- reconcile/gql_definitions/change_owners/queries/self_service_roles.py +5 -5
- reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +5 -5
- reconcile/gql_definitions/common/alerting_services_settings.py +5 -5
- reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
- reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
- reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_roles.py +5 -5
- reconcile/gql_definitions/common/app_interface_state_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
- reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
- reconcile/gql_definitions/common/apps.py +5 -5
- reconcile/gql_definitions/common/aws_vpc_requests.py +18 -5
- reconcile/gql_definitions/common/aws_vpcs.py +5 -5
- reconcile/gql_definitions/common/clusters.py +7 -5
- reconcile/gql_definitions/common/clusters_minimal.py +5 -5
- reconcile/gql_definitions/common/clusters_with_dms.py +5 -5
- reconcile/gql_definitions/common/clusters_with_peering.py +5 -5
- reconcile/gql_definitions/common/github_orgs.py +5 -5
- reconcile/gql_definitions/common/jira_settings.py +5 -5
- reconcile/gql_definitions/common/jiralert_settings.py +5 -5
- reconcile/gql_definitions/common/ldap_settings.py +5 -5
- reconcile/gql_definitions/common/namespaces.py +5 -5
- reconcile/gql_definitions/common/namespaces_minimal.py +7 -5
- reconcile/gql_definitions/common/ocm_env_telemeter.py +5 -5
- reconcile/gql_definitions/common/ocm_environments.py +5 -5
- reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
- reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -5
- reconcile/gql_definitions/common/pipeline_providers.py +5 -5
- reconcile/gql_definitions/common/quay_instances.py +5 -5
- reconcile/gql_definitions/common/quay_orgs.py +5 -5
- reconcile/gql_definitions/common/reserved_networks.py +5 -5
- reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
- reconcile/gql_definitions/common/saas_files.py +5 -5
- reconcile/gql_definitions/common/saas_target_namespaces.py +5 -5
- reconcile/gql_definitions/common/saasherder_settings.py +5 -5
- reconcile/gql_definitions/common/slack_workspaces.py +5 -5
- reconcile/gql_definitions/common/smtp_client_settings.py +5 -5
- reconcile/gql_definitions/common/state_aws_account.py +5 -5
- reconcile/gql_definitions/common/users.py +5 -5
- reconcile/gql_definitions/common/users_with_paths.py +5 -5
- reconcile/gql_definitions/cost_report/app_names.py +5 -5
- reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
- reconcile/gql_definitions/cost_report/settings.py +5 -5
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +5 -5
- reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +5 -5
- reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
- reconcile/gql_definitions/email_sender/apps.py +5 -5
- reconcile/gql_definitions/email_sender/emails.py +5 -5
- reconcile/gql_definitions/email_sender/users.py +5 -5
- reconcile/gql_definitions/endpoints_discovery/apps.py +5 -5
- reconcile/gql_definitions/external_resources/aws_accounts.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_modules.py +5 -5
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +38 -7
- reconcile/gql_definitions/external_resources/external_resources_settings.py +5 -5
- reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
- reconcile/gql_definitions/fleet_labeler/fleet_labels.py +5 -5
- reconcile/gql_definitions/fragments/aus_organization.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
- reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
- reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
- reconcile/gql_definitions/fragments/aws_organization.py +33 -0
- reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
- reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
- reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
- reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
- reconcile/gql_definitions/fragments/disable.py +5 -5
- reconcile/gql_definitions/fragments/email_service.py +5 -5
- reconcile/gql_definitions/fragments/email_user.py +5 -5
- reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
- reconcile/gql_definitions/fragments/membership_source.py +5 -5
- reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
- reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
- reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
- reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
- reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
- reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_values.py +5 -5
- reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
- reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
- reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
- reconcile/gql_definitions/fragments/terraform_state.py +5 -5
- reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
- reconcile/gql_definitions/fragments/user.py +5 -5
- reconcile/gql_definitions/fragments/vault_secret.py +5 -5
- reconcile/gql_definitions/gcp/gcp_docker_repos.py +5 -5
- reconcile/gql_definitions/gcp/gcp_projects.py +5 -5
- reconcile/gql_definitions/gitlab_members/gitlab_instances.py +5 -5
- reconcile/gql_definitions/gitlab_members/permissions.py +5 -5
- reconcile/gql_definitions/glitchtip/glitchtip_instance.py +5 -5
- reconcile/gql_definitions/glitchtip/glitchtip_project.py +5 -5
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +5 -5
- reconcile/gql_definitions/integrations/integrations.py +5 -5
- reconcile/gql_definitions/introspection.json +775 -136
- reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +5 -5
- reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +5 -5
- reconcile/gql_definitions/jira/jira_servers.py +5 -5
- reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +9 -5
- reconcile/gql_definitions/jumphosts/jumphosts.py +5 -5
- reconcile/gql_definitions/ldap_groups/roles.py +5 -5
- reconcile/gql_definitions/ldap_groups/settings.py +5 -5
- reconcile/gql_definitions/maintenance/maintenances.py +5 -5
- reconcile/gql_definitions/membershipsources/roles.py +5 -5
- reconcile/gql_definitions/ocm_labels/clusters.py +5 -5
- reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
- reconcile/gql_definitions/openshift_cluster_bots/clusters.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_roles.py +5 -5
- reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +5 -5
- reconcile/gql_definitions/quay_membership/quay_membership.py +5 -5
- reconcile/gql_definitions/rhcs/certs.py +25 -79
- reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
- reconcile/gql_definitions/rhidp/organizations.py +5 -5
- reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
- reconcile/gql_definitions/service_dependencies/service_dependencies.py +5 -5
- reconcile/gql_definitions/sharding/aws_accounts.py +5 -5
- reconcile/gql_definitions/sharding/ocm_organization.py +5 -5
- reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
- reconcile/gql_definitions/skupper_network/skupper_networks.py +5 -5
- reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
- reconcile/gql_definitions/slack_usergroups/permissions.py +5 -5
- reconcile/gql_definitions/slack_usergroups/users.py +5 -5
- reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
- reconcile/gql_definitions/status_board/status_board.py +5 -5
- reconcile/gql_definitions/statuspage/statuspages.py +5 -5
- reconcile/gql_definitions/templating/template_collection.py +5 -5
- reconcile/gql_definitions/templating/templates.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +5 -5
- reconcile/gql_definitions/terraform_init/aws_accounts.py +19 -5
- reconcile/gql_definitions/terraform_repo/terraform_repo.py +5 -5
- reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +37 -7
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +15 -5
- reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +5 -5
- reconcile/gql_definitions/vault_instances/vault_instances.py +5 -5
- reconcile/gql_definitions/vault_policies/vault_policies.py +5 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -5
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +6 -5
- reconcile/integrations_manager.py +3 -3
- reconcile/jenkins_worker_fleets.py +10 -8
- reconcile/jira_permissions_validator.py +237 -122
- reconcile/ldap_groups/integration.py +1 -1
- reconcile/ocm/types.py +35 -57
- reconcile/ocm_aws_infrastructure_access.py +1 -1
- reconcile/ocm_clusters.py +4 -4
- reconcile/ocm_labels/integration.py +3 -2
- reconcile/ocm_machine_pools.py +33 -27
- reconcile/openshift_base.py +113 -4
- reconcile/openshift_cluster_bots.py +1 -1
- reconcile/openshift_namespace_labels.py +1 -1
- reconcile/openshift_namespaces.py +96 -101
- reconcile/openshift_resources_base.py +6 -2
- reconcile/openshift_rhcs_certs.py +74 -37
- reconcile/openshift_rolebindings.py +7 -11
- reconcile/openshift_saas_deploy.py +4 -5
- reconcile/openshift_saas_deploy_change_tester.py +9 -7
- reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
- reconcile/openshift_serviceaccount_tokens.py +2 -2
- reconcile/openshift_upgrade_watcher.py +4 -4
- reconcile/oum/labelset.py +5 -3
- reconcile/oum/models.py +1 -4
- reconcile/prometheus_rules_tester/integration.py +3 -3
- reconcile/quay_base.py +25 -6
- reconcile/quay_membership.py +55 -29
- reconcile/quay_mirror.py +1 -1
- reconcile/quay_mirror_org.py +6 -4
- reconcile/quay_permissions.py +81 -75
- reconcile/quay_repos.py +35 -37
- reconcile/queries.py +132 -1
- reconcile/rhidp/common.py +3 -5
- reconcile/rhidp/sso_client/base.py +16 -5
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
- reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
- reconcile/skupper_network/integration.py +2 -2
- reconcile/slack_usergroups.py +35 -14
- reconcile/sql_query.py +1 -0
- reconcile/status_board.py +6 -6
- reconcile/statuspage/atlassian.py +7 -7
- reconcile/statuspage/integrations/maintenances.py +4 -3
- reconcile/statuspage/page.py +4 -9
- reconcile/statuspage/status.py +5 -8
- reconcile/templates/rosa-classic-cluster-creation.sh.j2 +1 -1
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
- reconcile/templating/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +2 -2
- reconcile/templating/validator.py +4 -4
- reconcile/terraform_aws_route53.py +7 -1
- reconcile/terraform_cloudflare_dns.py +3 -3
- reconcile/terraform_cloudflare_resources.py +5 -5
- reconcile/terraform_cloudflare_users.py +3 -2
- reconcile/terraform_init/integration.py +187 -23
- reconcile/terraform_repo.py +16 -12
- reconcile/terraform_resources.py +6 -6
- reconcile/terraform_tgw_attachments.py +27 -19
- reconcile/terraform_users.py +7 -0
- reconcile/terraform_vpc_peerings.py +14 -3
- reconcile/terraform_vpc_resources/integration.py +20 -8
- reconcile/terraform_vpc_resources/merge_request.py +12 -2
- reconcile/terraform_vpc_resources/merge_request_manager.py +43 -19
- reconcile/typed_queries/aws_account_tags.py +41 -0
- reconcile/typed_queries/cost_report/app_names.py +1 -1
- reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
- reconcile/typed_queries/saas_files.py +20 -15
- reconcile/typed_queries/status_board.py +2 -2
- reconcile/unleash_feature_toggles/integration.py +4 -2
- reconcile/utils/acs/base.py +6 -3
- reconcile/utils/acs/policies.py +2 -2
- reconcile/utils/aws_api.py +51 -20
- reconcile/utils/aws_api_typed/api.py +38 -9
- reconcile/utils/aws_api_typed/cloudformation.py +149 -0
- reconcile/utils/aws_api_typed/logs.py +73 -0
- reconcile/utils/aws_api_typed/organization.py +4 -2
- reconcile/utils/binary.py +7 -12
- reconcile/utils/datetime_util.py +67 -0
- reconcile/utils/deadmanssnitch_api.py +1 -1
- reconcile/utils/differ.py +2 -3
- reconcile/utils/early_exit_cache.py +11 -12
- reconcile/utils/environ.py +5 -0
- reconcile/utils/expiration.py +7 -3
- reconcile/utils/external_resource_spec.py +2 -0
- reconcile/utils/filtering.py +1 -1
- reconcile/utils/gitlab_api.py +19 -5
- reconcile/utils/glitchtip/client.py +6 -2
- reconcile/utils/glitchtip/models.py +25 -28
- reconcile/utils/gql.py +4 -7
- reconcile/utils/helpers.py +1 -1
- reconcile/utils/instrumented_wrappers.py +1 -1
- reconcile/utils/internal_groups/client.py +2 -2
- reconcile/utils/internal_groups/models.py +8 -17
- reconcile/utils/jinja2/utils.py +6 -101
- reconcile/utils/jira_client.py +82 -63
- reconcile/utils/jjb_client.py +26 -13
- reconcile/utils/jobcontroller/controller.py +2 -2
- reconcile/utils/jobcontroller/models.py +17 -1
- reconcile/utils/json.py +43 -1
- reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
- reconcile/utils/membershipsources/models.py +16 -23
- reconcile/utils/membershipsources/resolver.py +4 -2
- reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
- reconcile/utils/merge_request_manager/parser.py +6 -6
- reconcile/utils/metrics.py +5 -5
- reconcile/utils/models.py +304 -82
- reconcile/utils/mr/app_interface_reporter.py +2 -2
- reconcile/utils/mr/notificator.py +3 -3
- reconcile/utils/mr/update_access_report_base.py +3 -4
- reconcile/utils/mr/user_maintenance.py +3 -2
- reconcile/utils/oc.py +252 -201
- reconcile/utils/oc_filters.py +3 -3
- reconcile/utils/ocm/addons.py +0 -1
- reconcile/utils/ocm/base.py +17 -20
- reconcile/utils/ocm/cluster_groups.py +1 -1
- reconcile/utils/ocm/identity_providers.py +2 -2
- reconcile/utils/ocm/labels.py +1 -1
- reconcile/utils/ocm/products.py +8 -8
- reconcile/utils/ocm/search_filters.py +3 -6
- reconcile/utils/ocm/service_log.py +4 -6
- reconcile/utils/ocm/sre_capability_labels.py +20 -13
- reconcile/utils/openshift_resource.py +8 -3
- reconcile/utils/pagerduty_api.py +10 -7
- reconcile/utils/promotion_state.py +6 -11
- reconcile/utils/quay_api.py +74 -87
- reconcile/utils/raw_github_api.py +1 -1
- reconcile/utils/rhcsv2_certs.py +86 -23
- reconcile/utils/rosa/session.py +16 -0
- reconcile/utils/runtime/integration.py +2 -3
- reconcile/utils/runtime/runner.py +2 -2
- reconcile/utils/saasherder/interfaces.py +13 -20
- reconcile/utils/saasherder/models.py +23 -20
- reconcile/utils/saasherder/saasherder.py +50 -27
- reconcile/utils/slack_api.py +2 -2
- reconcile/utils/sloth.py +171 -2
- reconcile/utils/structs.py +1 -1
- reconcile/utils/terraform_client.py +5 -4
- reconcile/utils/terrascript_aws_client.py +274 -124
- reconcile/utils/unleash/server.py +2 -8
- reconcile/utils/vault.py +5 -12
- reconcile/utils/vcs.py +8 -8
- reconcile/vault_replication.py +107 -42
- reconcile/vpc_peerings_validator.py +13 -0
- tools/app_interface_reporter.py +4 -4
- tools/cli_commands/cost_report/cost_management_api.py +3 -3
- tools/cli_commands/cost_report/view.py +7 -6
- tools/cli_commands/erv2.py +1 -1
- tools/qontract_cli.py +28 -17
- tools/template_validation.py +3 -1
- {qontract_reconcile-0.10.2.dev361.dist-info → qontract_reconcile-0.10.2.dev474.dist-info}/entry_points.txt +0 -0
reconcile/utils/models.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from collections import UserList
|
|
2
2
|
from collections.abc import (
|
|
3
|
-
Callable,
|
|
4
|
-
Generator,
|
|
5
3
|
MutableMapping,
|
|
6
4
|
)
|
|
7
5
|
from typing import Any
|
|
@@ -9,75 +7,311 @@ from typing import Any
|
|
|
9
7
|
from croniter import croniter
|
|
10
8
|
from pydantic import (
|
|
11
9
|
BaseModel,
|
|
10
|
+
GetCoreSchemaHandler,
|
|
12
11
|
ValidationError,
|
|
13
12
|
)
|
|
14
|
-
from
|
|
15
|
-
from pydantic.fields import ModelField
|
|
13
|
+
from pydantic_core import core_schema
|
|
16
14
|
|
|
17
15
|
DEFAULT_STRING = "I was too lazy to define a string here"
|
|
18
16
|
DEFAULT_INT = 42
|
|
17
|
+
DEFAULT_BOOL = False
|
|
18
|
+
DEFAULT_JSON_STR = "{}"
|
|
19
|
+
DEFAULT_JSON: dict = {}
|
|
20
|
+
|
|
21
|
+
DataType = MutableMapping[str, Any] | str | int | bool | list | None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _process_field_with_alias(
|
|
25
|
+
field_info: dict[str, Any],
|
|
26
|
+
data: DataType,
|
|
27
|
+
alias: str | None,
|
|
28
|
+
use_defaults: bool,
|
|
29
|
+
definitions: list[dict[str, Any]] | None,
|
|
30
|
+
) -> DataType:
|
|
31
|
+
"""Process a field by recursively calling data_default_none and handling alias.
|
|
32
|
+
|
|
33
|
+
This helper function encapsulates the common pattern used by 'default',
|
|
34
|
+
'nullable', and 'model' field types where we need to:
|
|
35
|
+
1. Recursively process the field's schema
|
|
36
|
+
2. Handle data extraction via alias if data is a dict
|
|
37
|
+
3. Update the data dict with the processed value if alias exists
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
field_info: Schema information for the field to process
|
|
41
|
+
data: Input data (can be dict, primitive, list, or None)
|
|
42
|
+
alias: Field alias for dict key access, or None for unnamed fields
|
|
43
|
+
use_defaults: Whether to use default values for missing required fields
|
|
44
|
+
definitions: Schema definitions for definition-ref fields
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Processed data value, or updated data dict if alias was provided
|
|
48
|
+
"""
|
|
49
|
+
# Recursively process the field's schema
|
|
50
|
+
value = data_default_none(
|
|
51
|
+
field_info["schema"],
|
|
52
|
+
data.get(alias) if isinstance(data, MutableMapping) and alias else data,
|
|
53
|
+
use_defaults=use_defaults,
|
|
54
|
+
definitions=definitions,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# For unnamed fields (in recursive calls), return value directly
|
|
58
|
+
if not alias:
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
# For named fields, update the data dict
|
|
62
|
+
if not isinstance(data, MutableMapping):
|
|
63
|
+
raise TypeError(
|
|
64
|
+
f"Expected MutableMapping for field '{alias}', got {type(data).__name__}"
|
|
65
|
+
)
|
|
66
|
+
data[alias] = value
|
|
67
|
+
return data
|
|
19
68
|
|
|
20
69
|
|
|
21
70
|
def data_default_none(
|
|
22
|
-
klass: type[BaseModel]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
71
|
+
klass: type[BaseModel] | dict,
|
|
72
|
+
data: DataType,
|
|
73
|
+
use_defaults: bool = True,
|
|
74
|
+
definitions: list[dict[str, Any]] | None = None,
|
|
75
|
+
) -> DataType:
|
|
76
|
+
"""Set default values for required fields in Pydantic model data.
|
|
77
|
+
|
|
78
|
+
This function recursively processes Pydantic model schemas and fills in
|
|
79
|
+
missing fields with appropriate default values based on their type. It's
|
|
80
|
+
primarily used to handle GraphQL query results where fields may be missing.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
klass: Pydantic BaseModel class or schema dict to process
|
|
84
|
+
data: Input data to fill with defaults (can be dict, primitive, list, or None)
|
|
85
|
+
use_defaults: If True, use type-based defaults for missing required fields.
|
|
86
|
+
If False, raise ValueError for missing required fields.
|
|
87
|
+
definitions: Schema definitions for definition-ref fields (e.g., VaultSecret).
|
|
88
|
+
Automatically extracted from models with "definitions" schema type.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Data with defaults filled in. Return type matches input data structure.
|
|
92
|
+
|
|
93
|
+
Field handling behavior:
|
|
94
|
+
- Optional fields (T | None): Default to None if missing
|
|
95
|
+
- Required primitives: Use DEFAULT_STRING, DEFAULT_INT, or DEFAULT_BOOL
|
|
96
|
+
- Fields with explicit defaults: Use the provided default value
|
|
97
|
+
- Lists: Default to empty list [] if missing, recursively process items
|
|
98
|
+
- Nested models: Recursively process with data_default_none
|
|
99
|
+
- Union types: Try each variant until one validates ("first match wins")
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
>>> class User(BaseModel):
|
|
103
|
+
... name: str
|
|
104
|
+
... age: int | None
|
|
105
|
+
>>> data_default_none(User, {})
|
|
106
|
+
{'name': 'I was too lazy to define a string here', 'age': None}
|
|
107
|
+
"""
|
|
108
|
+
# =========================================================================
|
|
109
|
+
# Extract schema from BaseModel class or use provided schema dict
|
|
110
|
+
# =========================================================================
|
|
111
|
+
if isinstance(klass, dict):
|
|
112
|
+
# Recursive calls pass schema dict directly
|
|
113
|
+
schema = klass
|
|
114
|
+
else:
|
|
115
|
+
# Extract schema from Pydantic BaseModel class
|
|
116
|
+
match klass.__pydantic_core_schema__["type"]:
|
|
117
|
+
case "definitions":
|
|
118
|
+
# Models using definition-ref (e.g., VaultSecret fragments)
|
|
119
|
+
schema = klass.__pydantic_core_schema__["schema"]["schema"]
|
|
120
|
+
definitions = klass.__pydantic_core_schema__["definitions"]
|
|
121
|
+
case _:
|
|
122
|
+
# Standard models
|
|
123
|
+
schema = klass.__pydantic_core_schema__["schema"]
|
|
124
|
+
|
|
125
|
+
# =========================================================================
|
|
126
|
+
# Get fields to process - either model fields or single schema
|
|
127
|
+
# =========================================================================
|
|
128
|
+
if schema["type"] == "model-fields":
|
|
129
|
+
# Multiple fields in a model
|
|
130
|
+
fields = schema["fields"].items()
|
|
131
|
+
else:
|
|
132
|
+
# Single field schema (used in recursive calls for field processing)
|
|
133
|
+
fields = [(None, schema)]
|
|
134
|
+
|
|
135
|
+
# =========================================================================
|
|
136
|
+
# Process each field according to its schema type
|
|
137
|
+
# =========================================================================
|
|
138
|
+
for name, field_info in fields:
|
|
139
|
+
# Use alias if defined (e.g., "serverUrl" instead of "server_url")
|
|
140
|
+
alias = field_info.get("validation_alias", name)
|
|
141
|
+
|
|
142
|
+
match field_info["type"]:
|
|
143
|
+
# =================================================================
|
|
144
|
+
# PRIMITIVE TYPES: str, int, bool
|
|
145
|
+
# =================================================================
|
|
146
|
+
case "str" | "int" | "bool" | "json":
|
|
147
|
+
# If data is provided, return it as-is (don't override user data)
|
|
148
|
+
if data is not None:
|
|
149
|
+
return data
|
|
150
|
+
|
|
151
|
+
# If no defaults allowed, raise error for missing required field
|
|
152
|
+
if not use_defaults:
|
|
153
|
+
raise ValueError(f"Field {alias or ''} is required but not set.")
|
|
154
|
+
|
|
155
|
+
# Return type-specific default value
|
|
156
|
+
match field_info["type"]:
|
|
157
|
+
case "str":
|
|
158
|
+
return DEFAULT_STRING
|
|
159
|
+
case "int":
|
|
160
|
+
return DEFAULT_INT
|
|
161
|
+
case "bool":
|
|
162
|
+
return DEFAULT_BOOL
|
|
163
|
+
case "json":
|
|
164
|
+
return DEFAULT_JSON_STR
|
|
165
|
+
|
|
166
|
+
# =================================================================
|
|
167
|
+
# FIELDS WITH EXPLICIT DEFAULT VALUES
|
|
168
|
+
# =================================================================
|
|
169
|
+
case "default":
|
|
170
|
+
# If no data provided, use the field's default value
|
|
171
|
+
if data is None:
|
|
172
|
+
return field_info["default"]
|
|
173
|
+
|
|
174
|
+
# Data exists - recursively process it in case it's a complex type
|
|
175
|
+
return _process_field_with_alias(
|
|
176
|
+
field_info, data, alias, use_defaults, definitions
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# =================================================================
|
|
180
|
+
# NULLABLE/OPTIONAL FIELDS: T | None
|
|
181
|
+
# =================================================================
|
|
182
|
+
case "nullable":
|
|
183
|
+
# If no data provided, optional fields default to None
|
|
184
|
+
if data is None:
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
# Data exists - recursively process the non-None value
|
|
188
|
+
return _process_field_with_alias(
|
|
189
|
+
field_info, data, alias, use_defaults, definitions
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# =================================================================
|
|
193
|
+
# MODEL FIELDS: Nested Pydantic models within parent model
|
|
194
|
+
# =================================================================
|
|
195
|
+
case "model-field":
|
|
196
|
+
# Ensure data is a dict (create empty dict if needed)
|
|
197
|
+
# This allows processing models even when parent data is missing
|
|
198
|
+
if not isinstance(data, MutableMapping):
|
|
199
|
+
data = {}
|
|
200
|
+
|
|
201
|
+
# Recursively process the nested model
|
|
202
|
+
data[alias] = data_default_none(
|
|
203
|
+
field_info["schema"],
|
|
204
|
+
data.get(alias),
|
|
205
|
+
use_defaults=use_defaults,
|
|
206
|
+
definitions=definitions,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# =================================================================
|
|
210
|
+
# STANDALONE MODELS: BaseModel used as field type
|
|
211
|
+
# =================================================================
|
|
212
|
+
case "model":
|
|
213
|
+
# Recursively process nested BaseModel
|
|
214
|
+
return _process_field_with_alias(
|
|
215
|
+
field_info, data, alias, use_defaults, definitions
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# =================================================================
|
|
219
|
+
# DEFINITION REFERENCES: e.g., VaultSecret fragments
|
|
220
|
+
# =================================================================
|
|
221
|
+
case "definition-ref":
|
|
222
|
+
# Definition-ref fields require a MutableMapping data structure
|
|
223
|
+
assert isinstance(data, MutableMapping)
|
|
224
|
+
|
|
225
|
+
# Definitions are required for resolving references
|
|
226
|
+
if not definitions:
|
|
227
|
+
raise RuntimeError(
|
|
228
|
+
"definitions parameter is required for definition-ref fields"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Find the referenced schema definition by ref ID
|
|
232
|
+
ref_schema = next(
|
|
233
|
+
definition
|
|
234
|
+
for definition in definitions
|
|
235
|
+
if definition["ref"] == field_info["schema_ref"]
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Recursively process using the referenced schema
|
|
239
|
+
value = data_default_none(
|
|
240
|
+
ref_schema["schema"],
|
|
241
|
+
data.get(alias) if alias else data,
|
|
242
|
+
use_defaults=use_defaults,
|
|
243
|
+
definitions=definitions,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# For unnamed fields, return value directly
|
|
247
|
+
if not alias:
|
|
248
|
+
return value
|
|
249
|
+
|
|
250
|
+
data[alias] = value
|
|
251
|
+
|
|
252
|
+
# =================================================================
|
|
253
|
+
# UNION TYPES: T1 | T2 | ...
|
|
254
|
+
# =================================================================
|
|
255
|
+
case "union":
|
|
256
|
+
# Strategy: Try each union variant until one validates successfully
|
|
257
|
+
# This is a "first match wins" approach
|
|
258
|
+
for sub_field in field_info["choices"]:
|
|
259
|
+
try:
|
|
260
|
+
# Try to process data with this union variant
|
|
261
|
+
# use_defaults=False ensures we only match existing data
|
|
262
|
+
sub_data = data_default_none(
|
|
263
|
+
sub_field,
|
|
264
|
+
# don't alter data structure here
|
|
265
|
+
dict(data) if isinstance(data, MutableMapping) else data,
|
|
266
|
+
use_defaults=use_defaults,
|
|
267
|
+
definitions=definitions,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Validate the result matches the expected type
|
|
271
|
+
match sub_field["type"]:
|
|
272
|
+
case "str":
|
|
273
|
+
if not isinstance(sub_data, str):
|
|
274
|
+
raise ValueError()
|
|
275
|
+
case "int":
|
|
276
|
+
if not isinstance(sub_data, int):
|
|
277
|
+
raise ValueError()
|
|
278
|
+
case "bool":
|
|
279
|
+
if not isinstance(sub_data, bool):
|
|
280
|
+
raise ValueError()
|
|
281
|
+
case "model":
|
|
282
|
+
if not isinstance(sub_data, dict):
|
|
283
|
+
raise ValueError()
|
|
284
|
+
# Validate the dict can be instantiated as the model
|
|
285
|
+
sub_field["cls"].model_validate(sub_data)
|
|
286
|
+
if isinstance(data, MutableMapping):
|
|
287
|
+
data.update(sub_data)
|
|
288
|
+
|
|
289
|
+
# If we get here, this union variant matched successfully
|
|
290
|
+
return data
|
|
291
|
+
except (ValidationError, ValueError):
|
|
292
|
+
# This variant didn't match, try the next one
|
|
293
|
+
continue
|
|
294
|
+
|
|
295
|
+
# =================================================================
|
|
296
|
+
# LIST TYPES: list[T]
|
|
297
|
+
# =================================================================
|
|
298
|
+
case "list":
|
|
299
|
+
# If data is not a list, default to empty list
|
|
300
|
+
if not isinstance(data, list):
|
|
301
|
+
return []
|
|
302
|
+
|
|
303
|
+
# Recursively process each list item
|
|
304
|
+
return [
|
|
305
|
+
data_default_none(
|
|
306
|
+
field_info["items_schema"],
|
|
307
|
+
item.get(alias)
|
|
308
|
+
if isinstance(item, MutableMapping) and alias
|
|
309
|
+
else item,
|
|
310
|
+
use_defaults=use_defaults,
|
|
311
|
+
definitions=definitions,
|
|
312
|
+
)
|
|
313
|
+
for item in data
|
|
49
314
|
]
|
|
50
|
-
elif field.sub_fields:
|
|
51
|
-
if all(
|
|
52
|
-
isinstance(sub_field.type_, type)
|
|
53
|
-
and issubclass(sub_field.type_, BaseModel)
|
|
54
|
-
for sub_field in field.sub_fields
|
|
55
|
-
):
|
|
56
|
-
# Union[ClassA, ClassB] field
|
|
57
|
-
for sub_field in field.sub_fields:
|
|
58
|
-
if isinstance(data[field.alias], dict):
|
|
59
|
-
try:
|
|
60
|
-
d = dict(data[field.alias])
|
|
61
|
-
d.update(data_default_none(sub_field.type_, d))
|
|
62
|
-
# Lets confirm we found a matching union class
|
|
63
|
-
sub_field.type_(**d)
|
|
64
|
-
data[field.alias] = d
|
|
65
|
-
break
|
|
66
|
-
except ValidationError:
|
|
67
|
-
continue
|
|
68
|
-
elif isinstance(data[field.alias], list) and len(field.sub_fields) == 1:
|
|
69
|
-
# list[Union[ClassA, ClassB]] field
|
|
70
|
-
for sub_data in data[field.alias]:
|
|
71
|
-
for sub_field in field.sub_fields[0].sub_fields or []:
|
|
72
|
-
try:
|
|
73
|
-
d = dict(sub_data)
|
|
74
|
-
d.update(data_default_none(sub_field.type_, d))
|
|
75
|
-
# Lets confirm we found a matching union class
|
|
76
|
-
sub_field.type_(**d)
|
|
77
|
-
sub_data.update(d)
|
|
78
|
-
break
|
|
79
|
-
except ValidationError:
|
|
80
|
-
continue
|
|
81
315
|
|
|
82
316
|
return data
|
|
83
317
|
|
|
@@ -89,28 +323,16 @@ class CSV(UserList[str]):
|
|
|
89
323
|
"""
|
|
90
324
|
|
|
91
325
|
@classmethod
|
|
92
|
-
def
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
items = [] if not value else value.split(",")
|
|
99
|
-
return items
|
|
326
|
+
def __get_pydantic_core_schema__( # noqa: PLW3201
|
|
327
|
+
cls, source: type[Any], handler: GetCoreSchemaHandler
|
|
328
|
+
) -> core_schema.CoreSchema:
|
|
329
|
+
return core_schema.with_info_before_validator_function(
|
|
330
|
+
cls._validate, core_schema.list_schema()
|
|
331
|
+
)
|
|
100
332
|
|
|
101
333
|
@classmethod
|
|
102
|
-
def
|
|
103
|
-
|
|
104
|
-
) -> "list[str]":
|
|
105
|
-
min_items = field.field_info.extra.get("csv_min_items")
|
|
106
|
-
max_items = field.field_info.extra.get("csv_max_items")
|
|
107
|
-
|
|
108
|
-
v_len = len(v)
|
|
109
|
-
if min_items is not None and v_len < min_items:
|
|
110
|
-
raise pydantic_errors.ListMinLengthError(limit_value=min_items)
|
|
111
|
-
if max_items is not None and v_len > max_items:
|
|
112
|
-
raise pydantic_errors.ListMaxLengthError(limit_value=max_items)
|
|
113
|
-
return v
|
|
334
|
+
def _validate(cls, __input_value: str, _: Any) -> list[str]:
|
|
335
|
+
return [] if not __input_value else __input_value.split(",")
|
|
114
336
|
|
|
115
337
|
|
|
116
338
|
def cron_validator(value: str) -> str:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from collections.abc import Iterable
|
|
2
|
-
from datetime import datetime
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
|
|
5
4
|
from ruamel.yaml.scalarstring import PreservedScalarString
|
|
6
5
|
|
|
6
|
+
from reconcile.utils.datetime_util import utc_now
|
|
7
7
|
from reconcile.utils.gitlab_api import GitLabApi
|
|
8
8
|
from reconcile.utils.mr.base import (
|
|
9
9
|
MergeRequestBase,
|
|
@@ -26,7 +26,7 @@ class CreateAppInterfaceReporter(MergeRequestBase):
|
|
|
26
26
|
|
|
27
27
|
self.labels = [AUTO_MERGE]
|
|
28
28
|
|
|
29
|
-
now =
|
|
29
|
+
now = utc_now()
|
|
30
30
|
self.isodate = now.isoformat()
|
|
31
31
|
self.ts = now.strftime("%Y%m%d%H%M%S")
|
|
32
32
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from datetime import datetime
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
|
|
5
4
|
from pydantic import BaseModel
|
|
6
5
|
|
|
6
|
+
from reconcile.utils.datetime_util import utc_now
|
|
7
7
|
from reconcile.utils.gitlab_api import GitLabApi
|
|
8
8
|
from reconcile.utils.mr.base import (
|
|
9
9
|
MergeRequestBase,
|
|
@@ -35,7 +35,7 @@ class CreateAppInterfaceNotificator(MergeRequestBase):
|
|
|
35
35
|
email_base_path: Path = Path("data") / "app-interface" / "emails",
|
|
36
36
|
dry_run: bool = False,
|
|
37
37
|
):
|
|
38
|
-
self._notification_as_dict = notification.
|
|
38
|
+
self._notification_as_dict = notification.model_dump()
|
|
39
39
|
super().__init__()
|
|
40
40
|
self._notification = notification
|
|
41
41
|
self._email_base_path = email_base_path
|
|
@@ -58,7 +58,7 @@ class CreateAppInterfaceNotificator(MergeRequestBase):
|
|
|
58
58
|
)
|
|
59
59
|
|
|
60
60
|
def process(self, gitlab_cli: GitLabApi) -> None:
|
|
61
|
-
now =
|
|
61
|
+
now = utc_now()
|
|
62
62
|
ts = now.strftime("%Y%m%d%H%M%S")
|
|
63
63
|
short_date = now.strftime("%Y-%m-%d")
|
|
64
64
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from abc import abstractmethod
|
|
3
3
|
from collections.abc import Sequence
|
|
4
|
-
from datetime import UTC, date
|
|
5
|
-
from datetime import datetime as dt
|
|
6
4
|
from pathlib import Path
|
|
7
5
|
from typing import TypeVar
|
|
8
6
|
|
|
9
7
|
from jinja2 import Template
|
|
10
8
|
from pydantic import BaseModel
|
|
11
9
|
|
|
10
|
+
from reconcile.utils.datetime_util import utc_now
|
|
12
11
|
from reconcile.utils.gitlab_api import GitLabApi
|
|
13
12
|
from reconcile.utils.mr.base import MergeRequestBase
|
|
14
13
|
from reconcile.utils.mr.labels import AUTO_MERGE
|
|
@@ -27,7 +26,7 @@ class UpdateAccessReportBase(MergeRequestBase):
|
|
|
27
26
|
self.labels = [AUTO_MERGE]
|
|
28
27
|
self._users = users
|
|
29
28
|
self._workbook_file_name = str(workbook_path)
|
|
30
|
-
self._isodate =
|
|
29
|
+
self._isodate = utc_now().isoformat()
|
|
31
30
|
self._dry_run = dry_run
|
|
32
31
|
|
|
33
32
|
@property
|
|
@@ -65,7 +64,7 @@ class UpdateAccessReportBase(MergeRequestBase):
|
|
|
65
64
|
|
|
66
65
|
def _render_tracking_table_row(self, old_number_of_users: int) -> str:
|
|
67
66
|
# | Date Reviewed | Number of Current Users | +/- Red Hat Users |
|
|
68
|
-
return f"| {date
|
|
67
|
+
return f"| {utc_now().date()} | {len(self._users)} | {len(self._users) - old_number_of_users} |\n"
|
|
69
68
|
|
|
70
69
|
def _update_workbook(self, workbook_md: str) -> str:
|
|
71
70
|
new_workbook_md = ""
|
|
@@ -2,7 +2,7 @@ from collections.abc import Iterable
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from pydantic import BaseModel,
|
|
5
|
+
from pydantic import BaseModel, field_validator
|
|
6
6
|
from ruamel import yaml
|
|
7
7
|
|
|
8
8
|
from reconcile.utils.gitlab_api import GitLabApi
|
|
@@ -23,7 +23,8 @@ class PathSpec(BaseModel):
|
|
|
23
23
|
type: PathTypes
|
|
24
24
|
path: str
|
|
25
25
|
|
|
26
|
-
@
|
|
26
|
+
@field_validator("path")
|
|
27
|
+
@classmethod
|
|
27
28
|
def prepend_data_to_path(cls, v: str) -> str:
|
|
28
29
|
return "data" + v
|
|
29
30
|
|