qontract-reconcile 0.9.1rc298__py3-none-any.whl → 0.10.1.dev1203__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.1.dev1203.dist-info/METADATA +500 -0
- qontract_reconcile-0.10.1.dev1203.dist-info/RECORD +771 -0
- {qontract_reconcile-0.9.1rc298.dist-info → qontract_reconcile-0.10.1.dev1203.dist-info}/WHEEL +1 -2
- {qontract_reconcile-0.9.1rc298.dist-info → qontract_reconcile-0.10.1.dev1203.dist-info}/entry_points.txt +4 -2
- reconcile/acs_notifiers.py +126 -0
- reconcile/acs_policies.py +243 -0
- reconcile/acs_rbac.py +596 -0
- reconcile/aus/advanced_upgrade_service.py +621 -8
- reconcile/aus/aus_label_source.py +115 -0
- reconcile/aus/base.py +1053 -353
- reconcile/{utils → aus}/cluster_version_data.py +27 -12
- reconcile/aus/healthchecks.py +77 -0
- reconcile/aus/metrics.py +158 -0
- reconcile/aus/models.py +245 -5
- reconcile/aus/node_pool_spec.py +35 -0
- reconcile/aus/ocm_addons_upgrade_scheduler_org.py +225 -110
- reconcile/aus/ocm_upgrade_scheduler.py +76 -71
- reconcile/aus/ocm_upgrade_scheduler_org.py +81 -23
- reconcile/aus/version_gate_approver.py +204 -0
- reconcile/aus/version_gates/__init__.py +12 -0
- reconcile/aus/version_gates/handler.py +33 -0
- reconcile/aus/version_gates/ingress_gate_handler.py +32 -0
- reconcile/aus/version_gates/ocp_gate_handler.py +26 -0
- reconcile/aus/version_gates/sts_version_gate_handler.py +100 -0
- reconcile/aws_account_manager/README.md +5 -0
- reconcile/aws_account_manager/integration.py +373 -0
- reconcile/aws_account_manager/merge_request_manager.py +114 -0
- reconcile/aws_account_manager/metrics.py +39 -0
- reconcile/aws_account_manager/reconciler.py +403 -0
- reconcile/aws_account_manager/utils.py +41 -0
- reconcile/aws_ami_cleanup/integration.py +273 -0
- reconcile/aws_ami_share.py +18 -14
- reconcile/aws_cloudwatch_log_retention/integration.py +253 -0
- reconcile/aws_iam_keys.py +1 -1
- reconcile/aws_iam_password_reset.py +56 -20
- reconcile/aws_saml_idp/integration.py +204 -0
- reconcile/aws_saml_roles/integration.py +322 -0
- reconcile/aws_support_cases_sos.py +2 -2
- reconcile/aws_version_sync/integration.py +430 -0
- reconcile/aws_version_sync/merge_request_manager/merge_request.py +156 -0
- reconcile/aws_version_sync/merge_request_manager/merge_request_manager.py +160 -0
- reconcile/aws_version_sync/utils.py +64 -0
- reconcile/blackbox_exporter_endpoint_monitoring.py +2 -5
- reconcile/change_owners/README.md +34 -0
- reconcile/change_owners/approver.py +7 -9
- reconcile/change_owners/bundle.py +134 -9
- reconcile/change_owners/change_log_tracking.py +236 -0
- reconcile/change_owners/change_owners.py +204 -194
- reconcile/change_owners/change_types.py +183 -265
- reconcile/change_owners/changes.py +488 -0
- reconcile/change_owners/decision.py +120 -41
- reconcile/change_owners/diff.py +63 -92
- reconcile/change_owners/implicit_ownership.py +19 -16
- reconcile/change_owners/self_service_roles.py +158 -35
- reconcile/change_owners/tester.py +20 -18
- reconcile/checkpoint.py +4 -6
- reconcile/cli.py +1523 -242
- reconcile/closedbox_endpoint_monitoring_base.py +10 -17
- reconcile/cluster_auth_rhidp/integration.py +257 -0
- reconcile/cluster_deployment_mapper.py +2 -5
- reconcile/cna/assets/asset.py +4 -7
- reconcile/cna/assets/null.py +2 -5
- reconcile/cna/integration.py +2 -3
- reconcile/cna/state.py +6 -9
- reconcile/dashdotdb_base.py +31 -10
- reconcile/dashdotdb_cso.py +3 -6
- reconcile/dashdotdb_dora.py +530 -0
- reconcile/dashdotdb_dvo.py +10 -13
- reconcile/dashdotdb_slo.py +75 -19
- reconcile/database_access_manager.py +753 -0
- reconcile/deadmanssnitch.py +207 -0
- reconcile/dynatrace_token_provider/dependencies.py +69 -0
- reconcile/dynatrace_token_provider/integration.py +656 -0
- reconcile/dynatrace_token_provider/metrics.py +62 -0
- reconcile/dynatrace_token_provider/model.py +14 -0
- reconcile/dynatrace_token_provider/ocm.py +140 -0
- reconcile/dynatrace_token_provider/validate.py +48 -0
- reconcile/endpoints_discovery/integration.py +348 -0
- reconcile/endpoints_discovery/merge_request.py +96 -0
- reconcile/endpoints_discovery/merge_request_manager.py +178 -0
- reconcile/external_resources/aws.py +204 -0
- reconcile/external_resources/factories.py +163 -0
- reconcile/external_resources/integration.py +194 -0
- reconcile/external_resources/integration_secrets_sync.py +47 -0
- reconcile/external_resources/manager.py +405 -0
- reconcile/external_resources/meta.py +17 -0
- reconcile/external_resources/metrics.py +95 -0
- reconcile/external_resources/model.py +350 -0
- reconcile/external_resources/reconciler.py +265 -0
- reconcile/external_resources/secrets_sync.py +465 -0
- reconcile/external_resources/state.py +258 -0
- reconcile/gabi_authorized_users.py +19 -11
- reconcile/gcr_mirror.py +43 -34
- reconcile/github_org.py +4 -6
- reconcile/github_owners.py +1 -1
- reconcile/github_repo_invites.py +2 -5
- reconcile/gitlab_fork_compliance.py +14 -13
- reconcile/gitlab_housekeeping.py +185 -91
- reconcile/gitlab_labeler.py +15 -14
- reconcile/gitlab_members.py +126 -120
- reconcile/gitlab_owners.py +53 -66
- reconcile/gitlab_permissions.py +167 -6
- reconcile/glitchtip/README.md +150 -0
- reconcile/glitchtip/integration.py +99 -51
- reconcile/glitchtip/reconciler.py +99 -70
- reconcile/glitchtip_project_alerts/__init__.py +0 -0
- reconcile/glitchtip_project_alerts/integration.py +333 -0
- reconcile/glitchtip_project_dsn/integration.py +43 -43
- reconcile/gql_definitions/acs/__init__.py +0 -0
- reconcile/gql_definitions/acs/acs_instances.py +83 -0
- reconcile/gql_definitions/acs/acs_policies.py +239 -0
- reconcile/gql_definitions/acs/acs_rbac.py +111 -0
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +46 -8
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +38 -8
- reconcile/gql_definitions/app_interface_metrics_exporter/__init__.py +0 -0
- reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +61 -0
- reconcile/gql_definitions/aws_account_manager/__init__.py +0 -0
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +177 -0
- reconcile/gql_definitions/aws_ami_cleanup/__init__.py +0 -0
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +161 -0
- reconcile/gql_definitions/aws_saml_idp/__init__.py +0 -0
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +117 -0
- reconcile/gql_definitions/aws_saml_roles/__init__.py +0 -0
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +117 -0
- reconcile/gql_definitions/aws_saml_roles/roles.py +97 -0
- reconcile/gql_definitions/aws_version_sync/__init__.py +0 -0
- reconcile/gql_definitions/aws_version_sync/clusters.py +83 -0
- reconcile/gql_definitions/aws_version_sync/namespaces.py +143 -0
- reconcile/gql_definitions/change_owners/queries/change_types.py +16 -29
- reconcile/gql_definitions/change_owners/queries/self_service_roles.py +45 -11
- reconcile/gql_definitions/cluster_auth_rhidp/__init__.py +0 -0
- reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +128 -0
- reconcile/gql_definitions/cna/queries/cna_provisioners.py +6 -8
- reconcile/gql_definitions/cna/queries/cna_resources.py +3 -5
- reconcile/gql_definitions/common/alerting_services_settings.py +2 -2
- reconcile/gql_definitions/common/app_code_component_repos.py +9 -5
- reconcile/gql_definitions/{glitchtip/glitchtip_settings.py → common/app_interface_custom_messages.py} +14 -16
- reconcile/gql_definitions/common/app_interface_dms_settings.py +86 -0
- reconcile/gql_definitions/common/app_interface_repo_settings.py +2 -2
- reconcile/gql_definitions/common/app_interface_state_settings.py +3 -5
- reconcile/gql_definitions/common/app_interface_vault_settings.py +3 -5
- reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +120 -0
- reconcile/gql_definitions/common/apps.py +72 -0
- reconcile/gql_definitions/common/aws_vpc_requests.py +109 -0
- reconcile/gql_definitions/common/aws_vpcs.py +84 -0
- reconcile/gql_definitions/common/clusters.py +120 -254
- reconcile/gql_definitions/common/clusters_minimal.py +11 -35
- reconcile/gql_definitions/common/clusters_with_dms.py +72 -0
- reconcile/gql_definitions/common/clusters_with_peering.py +70 -98
- reconcile/gql_definitions/common/github_orgs.py +2 -2
- reconcile/gql_definitions/common/jira_settings.py +68 -0
- reconcile/gql_definitions/common/jiralert_settings.py +68 -0
- reconcile/gql_definitions/common/namespaces.py +74 -32
- reconcile/gql_definitions/common/namespaces_minimal.py +4 -10
- reconcile/gql_definitions/common/ocm_env_telemeter.py +95 -0
- reconcile/gql_definitions/common/ocm_environments.py +4 -2
- reconcile/gql_definitions/common/pagerduty_instances.py +5 -5
- reconcile/gql_definitions/common/pgp_reencryption_settings.py +5 -11
- reconcile/gql_definitions/common/pipeline_providers.py +45 -90
- reconcile/gql_definitions/common/quay_instances.py +64 -0
- reconcile/gql_definitions/common/quay_orgs.py +68 -0
- reconcile/gql_definitions/common/reserved_networks.py +94 -0
- reconcile/gql_definitions/common/saas_files.py +133 -95
- reconcile/gql_definitions/common/saas_target_namespaces.py +41 -26
- reconcile/gql_definitions/common/saasherder_settings.py +2 -2
- reconcile/gql_definitions/common/slack_workspaces.py +62 -0
- reconcile/gql_definitions/common/smtp_client_settings.py +2 -2
- reconcile/gql_definitions/common/state_aws_account.py +77 -0
- reconcile/gql_definitions/common/users.py +3 -2
- reconcile/gql_definitions/cost_report/__init__.py +0 -0
- reconcile/gql_definitions/cost_report/app_names.py +68 -0
- reconcile/gql_definitions/cost_report/cost_namespaces.py +86 -0
- reconcile/gql_definitions/cost_report/settings.py +77 -0
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +42 -12
- reconcile/gql_definitions/dynatrace_token_provider/__init__.py +0 -0
- reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +79 -0
- reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +84 -0
- reconcile/gql_definitions/endpoints_discovery/__init__.py +0 -0
- reconcile/gql_definitions/endpoints_discovery/namespaces.py +127 -0
- reconcile/gql_definitions/external_resources/__init__.py +0 -0
- reconcile/gql_definitions/external_resources/aws_accounts.py +73 -0
- reconcile/gql_definitions/external_resources/external_resources_modules.py +78 -0
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +1111 -0
- reconcile/gql_definitions/external_resources/external_resources_settings.py +98 -0
- reconcile/gql_definitions/fragments/aus_organization.py +34 -39
- reconcile/gql_definitions/fragments/aws_account_common.py +62 -0
- reconcile/gql_definitions/fragments/aws_account_managed.py +57 -0
- reconcile/gql_definitions/fragments/aws_account_sso.py +35 -0
- reconcile/gql_definitions/fragments/aws_infra_management_account.py +2 -2
- reconcile/gql_definitions/fragments/aws_vpc.py +47 -0
- reconcile/gql_definitions/fragments/aws_vpc_request.py +65 -0
- reconcile/gql_definitions/fragments/aws_vpc_request_subnet.py +29 -0
- reconcile/gql_definitions/fragments/deplopy_resources.py +7 -7
- reconcile/gql_definitions/fragments/disable.py +28 -0
- reconcile/gql_definitions/fragments/jumphost_common_fields.py +2 -2
- reconcile/gql_definitions/fragments/membership_source.py +47 -0
- reconcile/gql_definitions/fragments/minimal_ocm_organization.py +29 -0
- reconcile/gql_definitions/fragments/oc_connection_cluster.py +4 -9
- reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
- reconcile/gql_definitions/fragments/pipeline_provider_retention.py +30 -0
- reconcile/gql_definitions/fragments/prometheus_instance.py +48 -0
- reconcile/gql_definitions/fragments/resource_limits_requirements.py +29 -0
- reconcile/gql_definitions/fragments/{resource_requirements.py → resource_requests_requirements.py} +3 -3
- reconcile/gql_definitions/fragments/resource_values.py +2 -2
- reconcile/gql_definitions/fragments/saas_target_namespace.py +55 -12
- reconcile/gql_definitions/fragments/serviceaccount_token.py +38 -0
- reconcile/gql_definitions/fragments/terraform_state.py +36 -0
- reconcile/gql_definitions/fragments/upgrade_policy.py +5 -3
- reconcile/gql_definitions/fragments/user.py +3 -2
- reconcile/gql_definitions/fragments/vault_secret.py +2 -2
- reconcile/gql_definitions/gitlab_members/gitlab_instances.py +6 -2
- reconcile/gql_definitions/gitlab_members/permissions.py +3 -5
- reconcile/gql_definitions/glitchtip/glitchtip_instance.py +16 -2
- reconcile/gql_definitions/glitchtip/glitchtip_project.py +22 -23
- reconcile/gql_definitions/glitchtip_project_alerts/__init__.py +0 -0
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +173 -0
- reconcile/gql_definitions/integrations/integrations.py +62 -45
- reconcile/gql_definitions/introspection.json +51176 -0
- reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +13 -5
- reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +79 -0
- reconcile/gql_definitions/jira/__init__.py +0 -0
- reconcile/gql_definitions/jira/jira_servers.py +80 -0
- reconcile/gql_definitions/jira_permissions_validator/__init__.py +0 -0
- reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +131 -0
- reconcile/gql_definitions/jumphosts/jumphosts.py +3 -5
- reconcile/gql_definitions/ldap_groups/__init__.py +0 -0
- reconcile/gql_definitions/ldap_groups/roles.py +111 -0
- reconcile/gql_definitions/ldap_groups/settings.py +79 -0
- reconcile/gql_definitions/maintenance/__init__.py +0 -0
- reconcile/gql_definitions/maintenance/maintenances.py +101 -0
- reconcile/gql_definitions/membershipsources/__init__.py +0 -0
- reconcile/gql_definitions/membershipsources/roles.py +112 -0
- reconcile/gql_definitions/ocm_labels/__init__.py +0 -0
- reconcile/gql_definitions/ocm_labels/clusters.py +112 -0
- reconcile/gql_definitions/ocm_labels/organizations.py +78 -0
- reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
- reconcile/gql_definitions/openshift_cluster_bots/__init__.py +0 -0
- reconcile/gql_definitions/openshift_cluster_bots/clusters.py +126 -0
- reconcile/gql_definitions/openshift_groups/managed_groups.py +2 -2
- reconcile/gql_definitions/openshift_groups/managed_roles.py +3 -2
- reconcile/gql_definitions/openshift_serviceaccount_tokens/__init__.py +0 -0
- reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +132 -0
- reconcile/gql_definitions/quay_membership/quay_membership.py +3 -5
- reconcile/gql_definitions/rhidp/__init__.py +0 -0
- reconcile/gql_definitions/rhidp/organizations.py +96 -0
- reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +2 -2
- reconcile/gql_definitions/service_dependencies/service_dependencies.py +9 -31
- reconcile/gql_definitions/sharding/aws_accounts.py +2 -2
- reconcile/gql_definitions/sharding/ocm_organization.py +63 -0
- reconcile/gql_definitions/skupper_network/site_controller_template.py +2 -2
- reconcile/gql_definitions/skupper_network/skupper_networks.py +12 -38
- reconcile/gql_definitions/slack_usergroups/clusters.py +2 -2
- reconcile/gql_definitions/slack_usergroups/permissions.py +8 -15
- reconcile/gql_definitions/slack_usergroups/users.py +3 -2
- reconcile/gql_definitions/slo_documents/__init__.py +0 -0
- reconcile/gql_definitions/slo_documents/slo_documents.py +142 -0
- reconcile/gql_definitions/status_board/__init__.py +0 -0
- reconcile/gql_definitions/status_board/status_board.py +163 -0
- reconcile/gql_definitions/statuspage/statuspages.py +56 -7
- reconcile/gql_definitions/templating/__init__.py +0 -0
- reconcile/gql_definitions/templating/template_collection.py +130 -0
- reconcile/gql_definitions/templating/templates.py +108 -0
- reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +4 -8
- reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +8 -8
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +6 -8
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +45 -56
- reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +4 -8
- reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +4 -8
- reconcile/gql_definitions/terraform_init/__init__.py +0 -0
- reconcile/gql_definitions/terraform_init/aws_accounts.py +93 -0
- reconcile/gql_definitions/terraform_repo/__init__.py +0 -0
- reconcile/gql_definitions/terraform_repo/terraform_repo.py +141 -0
- reconcile/gql_definitions/terraform_resources/database_access_manager.py +158 -0
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +153 -162
- reconcile/gql_definitions/terraform_tgw_attachments/__init__.py +0 -0
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +119 -0
- reconcile/gql_definitions/unleash_feature_toggles/__init__.py +0 -0
- reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +113 -0
- reconcile/gql_definitions/vault_instances/vault_instances.py +17 -50
- reconcile/gql_definitions/vault_policies/vault_policies.py +2 -2
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +49 -12
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +7 -2
- reconcile/integrations_manager.py +25 -13
- reconcile/jenkins/types.py +5 -1
- reconcile/jenkins_base.py +36 -0
- reconcile/jenkins_job_builder.py +10 -48
- reconcile/jenkins_job_builds_cleaner.py +40 -25
- reconcile/jenkins_job_cleaner.py +1 -3
- reconcile/jenkins_roles.py +22 -26
- reconcile/jenkins_webhooks.py +9 -6
- reconcile/jenkins_worker_fleets.py +11 -6
- reconcile/jira_permissions_validator.py +340 -0
- reconcile/jira_watcher.py +3 -5
- reconcile/ldap_groups/__init__.py +0 -0
- reconcile/ldap_groups/integration.py +279 -0
- reconcile/ldap_users.py +3 -0
- reconcile/ocm/types.py +39 -59
- reconcile/ocm_additional_routers.py +0 -1
- reconcile/ocm_addons_upgrade_tests_trigger.py +10 -15
- reconcile/ocm_aws_infrastructure_access.py +30 -32
- reconcile/ocm_clusters.py +217 -130
- reconcile/ocm_external_configuration_labels.py +15 -0
- reconcile/ocm_github_idp.py +1 -1
- reconcile/ocm_groups.py +25 -5
- reconcile/ocm_internal_notifications/__init__.py +0 -0
- reconcile/ocm_internal_notifications/integration.py +119 -0
- reconcile/ocm_labels/__init__.py +0 -0
- reconcile/ocm_labels/integration.py +409 -0
- reconcile/ocm_machine_pools.py +517 -108
- reconcile/ocm_upgrade_scheduler_org_updater.py +15 -11
- reconcile/openshift_base.py +609 -207
- reconcile/openshift_cluster_bots.py +344 -0
- reconcile/openshift_clusterrolebindings.py +15 -15
- reconcile/openshift_groups.py +42 -45
- reconcile/openshift_limitranges.py +1 -0
- reconcile/openshift_namespace_labels.py +22 -28
- reconcile/openshift_namespaces.py +22 -22
- reconcile/openshift_network_policies.py +4 -8
- reconcile/openshift_prometheus_rules.py +43 -0
- reconcile/openshift_resourcequotas.py +2 -16
- reconcile/openshift_resources.py +12 -10
- reconcile/openshift_resources_base.py +304 -328
- reconcile/openshift_rolebindings.py +18 -20
- reconcile/openshift_saas_deploy.py +105 -21
- reconcile/openshift_saas_deploy_change_tester.py +30 -35
- reconcile/openshift_saas_deploy_trigger_base.py +39 -36
- reconcile/openshift_saas_deploy_trigger_cleaner.py +41 -27
- reconcile/openshift_saas_deploy_trigger_configs.py +1 -2
- reconcile/openshift_saas_deploy_trigger_images.py +1 -2
- reconcile/openshift_saas_deploy_trigger_moving_commits.py +1 -2
- reconcile/openshift_saas_deploy_trigger_upstream_jobs.py +1 -2
- reconcile/openshift_serviceaccount_tokens.py +138 -74
- reconcile/openshift_tekton_resources.py +89 -24
- reconcile/openshift_upgrade_watcher.py +110 -62
- reconcile/openshift_users.py +16 -15
- reconcile/openshift_vault_secrets.py +11 -6
- reconcile/oum/__init__.py +0 -0
- reconcile/oum/base.py +387 -0
- reconcile/oum/labelset.py +55 -0
- reconcile/oum/metrics.py +71 -0
- reconcile/oum/models.py +69 -0
- reconcile/oum/providers.py +59 -0
- reconcile/oum/standalone.py +196 -0
- reconcile/prometheus_rules_tester/integration.py +31 -23
- reconcile/quay_base.py +4 -1
- reconcile/quay_membership.py +1 -2
- reconcile/quay_mirror.py +111 -61
- reconcile/quay_mirror_org.py +34 -21
- reconcile/quay_permissions.py +7 -3
- reconcile/quay_repos.py +24 -32
- reconcile/queries.py +263 -198
- reconcile/query_validator.py +3 -5
- reconcile/resource_scraper.py +3 -4
- reconcile/{template_tester.py → resource_template_tester.py} +3 -3
- reconcile/rhidp/__init__.py +0 -0
- reconcile/rhidp/common.py +214 -0
- reconcile/rhidp/metrics.py +20 -0
- reconcile/rhidp/ocm_oidc_idp/__init__.py +0 -0
- reconcile/rhidp/ocm_oidc_idp/base.py +221 -0
- reconcile/rhidp/ocm_oidc_idp/integration.py +56 -0
- reconcile/rhidp/ocm_oidc_idp/metrics.py +22 -0
- reconcile/rhidp/sso_client/__init__.py +0 -0
- reconcile/rhidp/sso_client/base.py +266 -0
- reconcile/rhidp/sso_client/integration.py +60 -0
- reconcile/rhidp/sso_client/metrics.py +39 -0
- reconcile/run_integration.py +293 -0
- reconcile/saas_auto_promotions_manager/integration.py +69 -24
- reconcile/saas_auto_promotions_manager/merge_request_manager/batcher.py +208 -0
- reconcile/saas_auto_promotions_manager/merge_request_manager/desired_state.py +28 -0
- reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request.py +3 -4
- reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager_v2.py +172 -0
- reconcile/saas_auto_promotions_manager/merge_request_manager/metrics.py +42 -0
- reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py +226 -0
- reconcile/saas_auto_promotions_manager/merge_request_manager/open_merge_requests.py +23 -0
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +108 -32
- reconcile/saas_auto_promotions_manager/meta.py +4 -0
- reconcile/saas_auto_promotions_manager/publisher.py +32 -4
- reconcile/saas_auto_promotions_manager/s3_exporter.py +77 -0
- reconcile/saas_auto_promotions_manager/subscriber.py +110 -23
- reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py +48 -41
- reconcile/saas_file_validator.py +16 -6
- reconcile/sendgrid_teammates.py +27 -12
- reconcile/service_dependencies.py +0 -3
- reconcile/signalfx_endpoint_monitoring.py +2 -5
- reconcile/skupper_network/integration.py +10 -11
- reconcile/skupper_network/models.py +3 -5
- reconcile/skupper_network/reconciler.py +28 -35
- reconcile/skupper_network/site_controller.py +8 -8
- reconcile/slack_base.py +4 -7
- reconcile/slack_usergroups.py +249 -171
- reconcile/sql_query.py +324 -171
- reconcile/status.py +0 -1
- reconcile/status_board.py +275 -0
- reconcile/statuspage/__init__.py +0 -5
- reconcile/statuspage/atlassian.py +219 -80
- reconcile/statuspage/integration.py +9 -97
- reconcile/statuspage/integrations/__init__.py +0 -0
- reconcile/statuspage/integrations/components.py +77 -0
- reconcile/statuspage/integrations/maintenances.py +111 -0
- reconcile/statuspage/page.py +107 -72
- reconcile/statuspage/state.py +6 -11
- reconcile/statuspage/status.py +8 -12
- reconcile/templates/rosa-classic-cluster-creation.sh.j2 +60 -0
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +61 -0
- reconcile/templating/__init__.py +0 -0
- reconcile/templating/lib/__init__.py +0 -0
- reconcile/templating/lib/merge_request_manager.py +180 -0
- reconcile/templating/lib/model.py +20 -0
- reconcile/templating/lib/rendering.py +191 -0
- reconcile/templating/renderer.py +410 -0
- reconcile/templating/validator.py +153 -0
- reconcile/terraform_aws_route53.py +13 -10
- reconcile/terraform_cloudflare_dns.py +92 -122
- reconcile/terraform_cloudflare_resources.py +15 -13
- reconcile/terraform_cloudflare_users.py +27 -27
- reconcile/terraform_init/__init__.py +0 -0
- reconcile/terraform_init/integration.py +165 -0
- reconcile/terraform_init/merge_request.py +57 -0
- reconcile/terraform_init/merge_request_manager.py +102 -0
- reconcile/terraform_repo.py +403 -0
- reconcile/terraform_resources.py +266 -168
- reconcile/terraform_tgw_attachments.py +417 -167
- reconcile/terraform_users.py +40 -17
- reconcile/terraform_vpc_peerings.py +310 -142
- reconcile/terraform_vpc_resources/__init__.py +0 -0
- reconcile/terraform_vpc_resources/integration.py +220 -0
- reconcile/terraform_vpc_resources/merge_request.py +57 -0
- reconcile/terraform_vpc_resources/merge_request_manager.py +107 -0
- reconcile/typed_queries/alerting_services_settings.py +1 -2
- reconcile/typed_queries/app_interface_custom_messages.py +24 -0
- reconcile/typed_queries/app_interface_deadmanssnitch_settings.py +17 -0
- reconcile/typed_queries/app_interface_metrics_exporter/__init__.py +0 -0
- reconcile/typed_queries/app_interface_metrics_exporter/onboarding_status.py +13 -0
- reconcile/typed_queries/app_interface_repo_url.py +1 -2
- reconcile/typed_queries/app_interface_state_settings.py +1 -3
- reconcile/typed_queries/app_interface_vault_settings.py +1 -2
- reconcile/typed_queries/app_quay_repos_escalation_policies.py +14 -0
- reconcile/typed_queries/apps.py +11 -0
- reconcile/typed_queries/aws_vpc_requests.py +9 -0
- reconcile/typed_queries/aws_vpcs.py +12 -0
- reconcile/typed_queries/cloudflare.py +10 -0
- reconcile/typed_queries/clusters.py +7 -5
- reconcile/typed_queries/clusters_minimal.py +6 -5
- reconcile/typed_queries/clusters_with_dms.py +16 -0
- reconcile/typed_queries/cost_report/__init__.py +0 -0
- reconcile/typed_queries/cost_report/app_names.py +22 -0
- reconcile/typed_queries/cost_report/cost_namespaces.py +43 -0
- reconcile/typed_queries/cost_report/settings.py +15 -0
- reconcile/typed_queries/dynatrace.py +10 -0
- reconcile/typed_queries/dynatrace_environments.py +14 -0
- reconcile/typed_queries/dynatrace_token_provider_token_specs.py +14 -0
- reconcile/typed_queries/external_resources.py +46 -0
- reconcile/typed_queries/get_state_aws_account.py +20 -0
- reconcile/typed_queries/glitchtip.py +10 -0
- reconcile/typed_queries/jenkins.py +25 -0
- reconcile/typed_queries/jira.py +7 -0
- reconcile/typed_queries/jira_settings.py +16 -0
- reconcile/typed_queries/jiralert_settings.py +22 -0
- reconcile/typed_queries/ocm.py +8 -0
- reconcile/typed_queries/pagerduty_instances.py +2 -7
- reconcile/typed_queries/quay.py +23 -0
- reconcile/typed_queries/repos.py +20 -8
- reconcile/typed_queries/reserved_networks.py +12 -0
- reconcile/typed_queries/saas_files.py +221 -167
- reconcile/typed_queries/slack.py +7 -0
- reconcile/typed_queries/slo_documents.py +12 -0
- reconcile/typed_queries/status_board.py +58 -0
- reconcile/typed_queries/tekton_pipeline_providers.py +1 -2
- reconcile/typed_queries/terraform_namespaces.py +1 -2
- reconcile/typed_queries/terraform_tgw_attachments/__init__.py +0 -0
- reconcile/typed_queries/terraform_tgw_attachments/aws_accounts.py +16 -0
- reconcile/typed_queries/unleash.py +10 -0
- reconcile/typed_queries/users.py +11 -0
- reconcile/typed_queries/vault.py +10 -0
- reconcile/unleash_feature_toggles/__init__.py +0 -0
- reconcile/unleash_feature_toggles/integration.py +287 -0
- reconcile/utils/acs/__init__.py +0 -0
- reconcile/utils/acs/base.py +81 -0
- reconcile/utils/acs/notifiers.py +143 -0
- reconcile/utils/acs/policies.py +163 -0
- reconcile/utils/acs/rbac.py +277 -0
- reconcile/utils/aggregated_list.py +11 -9
- reconcile/utils/amtool.py +6 -4
- reconcile/utils/aws_api.py +279 -66
- reconcile/utils/aws_api_typed/__init__.py +0 -0
- reconcile/utils/aws_api_typed/account.py +23 -0
- reconcile/utils/aws_api_typed/api.py +273 -0
- reconcile/utils/aws_api_typed/dynamodb.py +16 -0
- reconcile/utils/aws_api_typed/iam.py +67 -0
- reconcile/utils/aws_api_typed/organization.py +152 -0
- reconcile/utils/aws_api_typed/s3.py +26 -0
- reconcile/utils/aws_api_typed/service_quotas.py +79 -0
- reconcile/utils/aws_api_typed/sts.py +36 -0
- reconcile/utils/aws_api_typed/support.py +79 -0
- reconcile/utils/aws_helper.py +42 -3
- reconcile/utils/batches.py +11 -0
- reconcile/utils/binary.py +7 -9
- reconcile/utils/cloud_resource_best_practice/__init__.py +0 -0
- reconcile/utils/cloud_resource_best_practice/aws_rds.py +66 -0
- reconcile/utils/clusterhealth/__init__.py +0 -0
- reconcile/utils/clusterhealth/providerbase.py +39 -0
- reconcile/utils/clusterhealth/telemeter.py +39 -0
- reconcile/utils/config.py +3 -4
- reconcile/utils/deadmanssnitch_api.py +86 -0
- reconcile/utils/differ.py +205 -0
- reconcile/utils/disabled_integrations.py +4 -6
- reconcile/utils/dynatrace/__init__.py +0 -0
- reconcile/utils/dynatrace/client.py +93 -0
- reconcile/utils/early_exit_cache.py +289 -0
- reconcile/utils/elasticsearch_exceptions.py +5 -0
- reconcile/utils/environ.py +2 -2
- reconcile/utils/exceptions.py +4 -0
- reconcile/utils/expiration.py +4 -8
- reconcile/utils/extended_early_exit.py +210 -0
- reconcile/utils/external_resource_spec.py +34 -12
- reconcile/utils/external_resources.py +48 -20
- reconcile/utils/filtering.py +16 -0
- reconcile/utils/git.py +49 -16
- reconcile/utils/github_api.py +10 -9
- reconcile/utils/gitlab_api.py +333 -190
- reconcile/utils/glitchtip/client.py +97 -100
- reconcile/utils/glitchtip/models.py +89 -11
- reconcile/utils/gql.py +157 -58
- reconcile/utils/grouping.py +17 -0
- reconcile/utils/helm.py +89 -18
- reconcile/utils/helpers.py +51 -0
- reconcile/utils/imap_client.py +5 -6
- reconcile/utils/internal_groups/__init__.py +0 -0
- reconcile/utils/internal_groups/client.py +160 -0
- reconcile/utils/internal_groups/models.py +71 -0
- reconcile/utils/jenkins_api.py +10 -34
- reconcile/utils/jinja2/__init__.py +0 -0
- reconcile/utils/{jinja2_ext.py → jinja2/extensions.py} +6 -4
- reconcile/utils/jinja2/filters.py +142 -0
- reconcile/utils/jinja2/utils.py +278 -0
- reconcile/utils/jira_client.py +165 -8
- reconcile/utils/jjb_client.py +47 -35
- reconcile/utils/jobcontroller/__init__.py +0 -0
- reconcile/utils/jobcontroller/controller.py +413 -0
- reconcile/utils/jobcontroller/models.py +195 -0
- reconcile/utils/jsonpath.py +4 -5
- reconcile/utils/jump_host.py +13 -12
- reconcile/utils/keycloak.py +106 -0
- reconcile/utils/ldap_client.py +35 -6
- reconcile/utils/lean_terraform_client.py +115 -6
- reconcile/utils/membershipsources/__init__.py +0 -0
- reconcile/utils/membershipsources/app_interface_resolver.py +60 -0
- reconcile/utils/membershipsources/models.py +91 -0
- reconcile/utils/membershipsources/resolver.py +110 -0
- reconcile/utils/merge_request_manager/__init__.py +0 -0
- reconcile/utils/merge_request_manager/merge_request_manager.py +99 -0
- reconcile/utils/merge_request_manager/parser.py +67 -0
- reconcile/utils/metrics.py +511 -1
- reconcile/utils/models.py +123 -0
- reconcile/utils/mr/README.md +198 -0
- reconcile/utils/mr/__init__.py +14 -10
- reconcile/utils/mr/app_interface_reporter.py +2 -2
- reconcile/utils/mr/aws_access.py +4 -4
- reconcile/utils/mr/base.py +51 -31
- reconcile/utils/mr/clusters_updates.py +10 -7
- reconcile/utils/mr/glitchtip_access_reporter.py +2 -4
- reconcile/utils/mr/labels.py +14 -1
- reconcile/utils/mr/notificator.py +1 -3
- reconcile/utils/mr/ocm_update_recommended_version.py +1 -2
- reconcile/utils/mr/ocm_upgrade_scheduler_org_updates.py +7 -3
- reconcile/utils/mr/promote_qontract.py +203 -0
- reconcile/utils/mr/user_maintenance.py +24 -4
- reconcile/utils/oauth2_backend_application_session.py +132 -0
- reconcile/utils/oc.py +194 -170
- reconcile/utils/oc_connection_parameters.py +40 -51
- reconcile/utils/oc_filters.py +11 -13
- reconcile/utils/oc_map.py +14 -35
- reconcile/utils/ocm/__init__.py +30 -1
- reconcile/utils/ocm/addons.py +228 -0
- reconcile/utils/ocm/base.py +618 -5
- reconcile/utils/ocm/cluster_groups.py +5 -56
- reconcile/utils/ocm/clusters.py +111 -99
- reconcile/utils/ocm/identity_providers.py +66 -0
- reconcile/utils/ocm/label_sources.py +75 -0
- reconcile/utils/ocm/labels.py +139 -54
- reconcile/utils/ocm/manifests.py +39 -0
- reconcile/utils/ocm/ocm.py +182 -928
- reconcile/utils/ocm/products.py +758 -0
- reconcile/utils/ocm/search_filters.py +20 -28
- reconcile/utils/ocm/service_log.py +32 -79
- reconcile/utils/ocm/sre_capability_labels.py +51 -0
- reconcile/utils/ocm/status_board.py +66 -0
- reconcile/utils/ocm/subscriptions.py +49 -59
- reconcile/utils/ocm/syncsets.py +39 -0
- reconcile/utils/ocm/upgrades.py +181 -0
- reconcile/utils/ocm_base_client.py +71 -36
- reconcile/utils/openshift_resource.py +113 -67
- reconcile/utils/output.py +18 -11
- reconcile/utils/pagerduty_api.py +16 -10
- reconcile/utils/parse_dhms_duration.py +13 -1
- reconcile/utils/prometheus.py +123 -0
- reconcile/utils/promotion_state.py +56 -19
- reconcile/utils/promtool.py +5 -8
- reconcile/utils/quay_api.py +13 -25
- reconcile/utils/raw_github_api.py +3 -5
- reconcile/utils/repo_owners.py +2 -8
- reconcile/utils/rest_api_base.py +126 -0
- reconcile/utils/rosa/__init__.py +0 -0
- reconcile/utils/rosa/rosa_cli.py +310 -0
- reconcile/utils/rosa/session.py +201 -0
- reconcile/utils/ruamel.py +16 -0
- reconcile/utils/runtime/__init__.py +0 -1
- reconcile/utils/runtime/desired_state_diff.py +9 -20
- reconcile/utils/runtime/environment.py +33 -8
- reconcile/utils/runtime/integration.py +28 -12
- reconcile/utils/runtime/meta.py +1 -3
- reconcile/utils/runtime/runner.py +8 -11
- reconcile/utils/runtime/sharding.py +93 -36
- reconcile/utils/saasherder/__init__.py +1 -1
- reconcile/utils/saasherder/interfaces.py +143 -138
- reconcile/utils/saasherder/models.py +201 -43
- reconcile/utils/saasherder/saasherder.py +508 -378
- reconcile/utils/secret_reader.py +22 -27
- reconcile/utils/semver_helper.py +15 -1
- reconcile/utils/slack_api.py +124 -36
- reconcile/utils/smtp_client.py +1 -2
- reconcile/utils/sqs_gateway.py +10 -6
- reconcile/utils/state.py +276 -127
- reconcile/utils/terraform/config_client.py +6 -7
- reconcile/utils/terraform_client.py +284 -125
- reconcile/utils/terrascript/cloudflare_client.py +38 -17
- reconcile/utils/terrascript/cloudflare_resources.py +67 -18
- reconcile/utils/terrascript/models.py +2 -3
- reconcile/utils/terrascript/resources.py +1 -2
- reconcile/utils/terrascript_aws_client.py +1292 -540
- reconcile/utils/three_way_diff_strategy.py +157 -0
- reconcile/utils/unleash/__init__.py +11 -0
- reconcile/utils/{unleash.py → unleash/client.py} +35 -29
- reconcile/utils/unleash/server.py +145 -0
- reconcile/utils/vault.py +42 -32
- reconcile/utils/vaultsecretref.py +2 -4
- reconcile/utils/vcs.py +250 -0
- reconcile/vault_replication.py +38 -31
- reconcile/vpc_peerings_validator.py +82 -13
- tools/app_interface_metrics_exporter.py +70 -0
- tools/app_interface_reporter.py +44 -157
- tools/cli_commands/container_images_report.py +154 -0
- tools/cli_commands/cost_report/__init__.py +0 -0
- tools/cli_commands/cost_report/aws.py +137 -0
- tools/cli_commands/cost_report/cost_management_api.py +155 -0
- tools/cli_commands/cost_report/model.py +49 -0
- tools/cli_commands/cost_report/openshift.py +166 -0
- tools/cli_commands/cost_report/openshift_cost_optimization.py +187 -0
- tools/cli_commands/cost_report/response.py +124 -0
- tools/cli_commands/cost_report/util.py +72 -0
- tools/cli_commands/cost_report/view.py +524 -0
- tools/cli_commands/erv2.py +620 -0
- tools/cli_commands/gpg_encrypt.py +5 -8
- tools/cli_commands/systems_and_tools.py +489 -0
- tools/glitchtip_access_revalidation.py +1 -1
- tools/qontract_cli.py +2301 -673
- tools/saas_metrics_exporter/__init__.py +0 -0
- tools/saas_metrics_exporter/commit_distance/__init__.py +0 -0
- tools/saas_metrics_exporter/commit_distance/channel.py +63 -0
- tools/saas_metrics_exporter/commit_distance/commit_distance.py +103 -0
- tools/saas_metrics_exporter/commit_distance/metrics.py +19 -0
- tools/saas_metrics_exporter/main.py +99 -0
- tools/saas_promotion_state/__init__.py +0 -0
- tools/saas_promotion_state/saas_promotion_state.py +105 -0
- tools/sd_app_sre_alert_report.py +145 -0
- tools/template_validation.py +107 -0
- e2e_tests/cli.py +0 -83
- e2e_tests/create_namespace.py +0 -43
- e2e_tests/dedicated_admin_rolebindings.py +0 -44
- e2e_tests/dedicated_admin_test_base.py +0 -39
- e2e_tests/default_network_policies.py +0 -47
- e2e_tests/default_project_labels.py +0 -52
- e2e_tests/network_policy_test_base.py +0 -17
- e2e_tests/test_base.py +0 -56
- qontract_reconcile-0.9.1rc298.dist-info/METADATA +0 -63
- qontract_reconcile-0.9.1rc298.dist-info/RECORD +0 -585
- qontract_reconcile-0.9.1rc298.dist-info/top_level.txt +0 -4
- reconcile/ecr_mirror.py +0 -152
- reconcile/github_scanner.py +0 -74
- reconcile/gitlab_integrations.py +0 -63
- reconcile/gql_definitions/ocm_oidc_idp/clusters.py +0 -195
- reconcile/gql_definitions/ocp_release_mirror/ocp_release_mirror.py +0 -287
- reconcile/integrations_validator.py +0 -18
- reconcile/jenkins_plugins.py +0 -129
- reconcile/kafka_clusters.py +0 -208
- reconcile/ocm_cluster_admin.py +0 -42
- reconcile/ocm_oidc_idp.py +0 -198
- reconcile/ocp_release_mirror.py +0 -373
- reconcile/prometheus_rules_tester_old.py +0 -436
- reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager.py +0 -279
- reconcile/saas_auto_promotions_manager/utils/vcs.py +0 -141
- reconcile/sentry_config.py +0 -613
- reconcile/sentry_helper.py +0 -69
- reconcile/test/conftest.py +0 -187
- reconcile/test/fixtures.py +0 -24
- reconcile/test/saas_auto_promotions_manager/conftest.py +0 -69
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py +0 -110
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/data_keys.py +0 -10
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_housekeeping.py +0 -200
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py +0 -151
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/conftest.py +0 -63
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/data_keys.py +0 -4
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_multiple_namespaces.py +0 -46
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_namespace.py +0 -94
- reconcile/test/saas_auto_promotions_manager/merge_request_manager/renderer/test_content_single_target.py +0 -44
- reconcile/test/saas_auto_promotions_manager/subscriber/conftest.py +0 -74
- reconcile/test/saas_auto_promotions_manager/subscriber/data_keys.py +0 -11
- reconcile/test/saas_auto_promotions_manager/subscriber/test_content_hash.py +0 -155
- reconcile/test/saas_auto_promotions_manager/subscriber/test_diff.py +0 -173
- reconcile/test/saas_auto_promotions_manager/subscriber/test_multiple_channels_config_hash.py +0 -226
- reconcile/test/saas_auto_promotions_manager/subscriber/test_multiple_channels_moving_ref.py +0 -224
- reconcile/test/saas_auto_promotions_manager/subscriber/test_single_channel_with_single_publisher.py +0 -350
- reconcile/test/saas_auto_promotions_manager/test_integration_test.py +0 -129
- reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_multiple_publishers_for_single_channel.py +0 -70
- reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_saas_files_use_target_config_hash.py +0 -63
- reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_saas_files_with_auto_promote.py +0 -74
- reconcile/test/saas_auto_promotions_manager/utils/saas_files_inventory/test_saas_files_without_auto_promote.py +0 -65
- reconcile/test/test_aggregated_list.py +0 -237
- reconcile/test/test_amtool.py +0 -37
- reconcile/test/test_auto_promoter.py +0 -295
- reconcile/test/test_aws_ami_share.py +0 -68
- reconcile/test/test_aws_iam_keys.py +0 -70
- reconcile/test/test_aws_iam_password_reset.py +0 -35
- reconcile/test/test_aws_support_cases_sos.py +0 -23
- reconcile/test/test_checkpoint.py +0 -178
- reconcile/test/test_cli.py +0 -41
- reconcile/test/test_closedbox_endpoint_monitoring.py +0 -207
- reconcile/test/test_gabi_authorized_users.py +0 -72
- reconcile/test/test_github_org.py +0 -154
- reconcile/test/test_github_repo_invites.py +0 -123
- reconcile/test/test_gitlab_housekeeping.py +0 -88
- reconcile/test/test_gitlab_labeler.py +0 -129
- reconcile/test/test_gitlab_members.py +0 -283
- reconcile/test/test_instrumented_wrappers.py +0 -18
- reconcile/test/test_integrations_manager.py +0 -995
- reconcile/test/test_jenkins_worker_fleets.py +0 -55
- reconcile/test/test_jump_host.py +0 -117
- reconcile/test/test_ldap_users.py +0 -123
- reconcile/test/test_make.py +0 -28
- reconcile/test/test_ocm_additional_routers.py +0 -134
- reconcile/test/test_ocm_addons_upgrade_scheduler_org.py +0 -149
- reconcile/test/test_ocm_clusters.py +0 -598
- reconcile/test/test_ocm_clusters_manifest_updates.py +0 -89
- reconcile/test/test_ocm_oidc_idp.py +0 -315
- reconcile/test/test_ocm_update_recommended_version.py +0 -145
- reconcile/test/test_ocm_upgrade_scheduler.py +0 -614
- reconcile/test/test_ocm_upgrade_scheduler_org_updater.py +0 -129
- reconcile/test/test_openshift_base.py +0 -730
- reconcile/test/test_openshift_namespace_labels.py +0 -345
- reconcile/test/test_openshift_namespaces.py +0 -256
- reconcile/test/test_openshift_resource.py +0 -415
- reconcile/test/test_openshift_resources_base.py +0 -440
- reconcile/test/test_openshift_saas_deploy_change_tester.py +0 -310
- reconcile/test/test_openshift_tekton_resources.py +0 -253
- reconcile/test/test_openshift_upgrade_watcher.py +0 -146
- reconcile/test/test_prometheus_rules_tester.py +0 -151
- reconcile/test/test_prometheus_rules_tester_old.py +0 -77
- reconcile/test/test_quay_membership.py +0 -86
- reconcile/test/test_quay_mirror.py +0 -109
- reconcile/test/test_quay_mirror_org.py +0 -70
- reconcile/test/test_quay_repos.py +0 -59
- reconcile/test/test_queries.py +0 -53
- reconcile/test/test_repo_owners.py +0 -47
- reconcile/test/test_requests_sender.py +0 -139
- reconcile/test/test_saasherder.py +0 -1074
- reconcile/test/test_saasherder_allowed_secret_paths.py +0 -127
- reconcile/test/test_secret_reader.py +0 -153
- reconcile/test/test_slack_base.py +0 -185
- reconcile/test/test_slack_usergroups.py +0 -744
- reconcile/test/test_sql_query.py +0 -19
- reconcile/test/test_terraform_cloudflare_dns.py +0 -117
- reconcile/test/test_terraform_cloudflare_resources.py +0 -106
- reconcile/test/test_terraform_cloudflare_users.py +0 -749
- reconcile/test/test_terraform_resources.py +0 -257
- reconcile/test/test_terraform_tgw_attachments.py +0 -631
- reconcile/test/test_terraform_users.py +0 -57
- reconcile/test/test_terraform_vpc_peerings.py +0 -499
- reconcile/test/test_terraform_vpc_peerings_build_desired_state.py +0 -1061
- reconcile/test/test_unleash.py +0 -138
- reconcile/test/test_utils_aws_api.py +0 -240
- reconcile/test/test_utils_aws_helper.py +0 -80
- reconcile/test/test_utils_cluster_version_data.py +0 -177
- reconcile/test/test_utils_data_structures.py +0 -13
- reconcile/test/test_utils_disabled_integrations.py +0 -86
- reconcile/test/test_utils_expiration.py +0 -109
- reconcile/test/test_utils_external_resource_spec.py +0 -383
- reconcile/test/test_utils_external_resources.py +0 -247
- reconcile/test/test_utils_github_api.py +0 -73
- reconcile/test/test_utils_gitlab_api.py +0 -20
- reconcile/test/test_utils_gpg.py +0 -69
- reconcile/test/test_utils_gql.py +0 -81
- reconcile/test/test_utils_helm.py +0 -306
- reconcile/test/test_utils_helpers.py +0 -55
- reconcile/test/test_utils_imap_client.py +0 -65
- reconcile/test/test_utils_jjb_client.py +0 -52
- reconcile/test/test_utils_jsonpath.py +0 -286
- reconcile/test/test_utils_ldap_client.py +0 -51
- reconcile/test/test_utils_mr.py +0 -226
- reconcile/test/test_utils_mr_clusters_updates.py +0 -77
- reconcile/test/test_utils_oc.py +0 -984
- reconcile/test/test_utils_ocm.py +0 -110
- reconcile/test/test_utils_pagerduty_api.py +0 -251
- reconcile/test/test_utils_parse_dhms_duration.py +0 -34
- reconcile/test/test_utils_password_validator.py +0 -155
- reconcile/test/test_utils_quay_api.py +0 -86
- reconcile/test/test_utils_semver_helper.py +0 -19
- reconcile/test/test_utils_sharding.py +0 -56
- reconcile/test/test_utils_slack_api.py +0 -439
- reconcile/test/test_utils_smtp_client.py +0 -73
- reconcile/test/test_utils_state.py +0 -256
- reconcile/test/test_utils_terraform.py +0 -13
- reconcile/test/test_utils_terraform_client.py +0 -585
- reconcile/test/test_utils_terraform_config_client.py +0 -219
- reconcile/test/test_utils_terrascript_aws_client.py +0 -277
- reconcile/test/test_utils_terrascript_cloudflare_client.py +0 -597
- reconcile/test/test_utils_terrascript_cloudflare_resources.py +0 -26
- reconcile/test/test_vault_replication.py +0 -515
- reconcile/test/test_vault_utils.py +0 -47
- reconcile/test/test_version_bump.py +0 -18
- reconcile/test/test_vpc_peerings_validator.py +0 -103
- reconcile/test/test_wrong_region.py +0 -78
- reconcile/typed_queries/glitchtip_settings.py +0 -18
- reconcile/typed_queries/ocp_release_mirror.py +0 -11
- reconcile/unleash_watcher.py +0 -120
- reconcile/utils/git_secrets.py +0 -63
- reconcile/utils/mr/auto_promoter.py +0 -218
- reconcile/utils/sentry_client.py +0 -383
- release/test_version.py +0 -50
- release/version.py +0 -100
- tools/test/test_qontract_cli.py +0 -60
- tools/test/test_sre_checkpoints.py +0 -79
- /e2e_tests/__init__.py → /reconcile/aus/upgrades.py +0 -0
- /reconcile/{gql_definitions/ocp_release_mirror → aws_account_manager}/__init__.py +0 -0
- /reconcile/{test → aws_ami_cleanup}/__init__.py +0 -0
- /reconcile/{test/saas_auto_promotions_manager → aws_cloudwatch_log_retention}/__init__.py +0 -0
- /reconcile/{test/saas_auto_promotions_manager/merge_request_manager → aws_saml_idp}/__init__.py +0 -0
- /reconcile/{test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager → aws_saml_roles}/__init__.py +0 -0
- /reconcile/{test/saas_auto_promotions_manager/merge_request_manager/renderer → aws_version_sync}/__init__.py +0 -0
- /reconcile/{test/saas_auto_promotions_manager/subscriber → aws_version_sync/merge_request_manager}/__init__.py +0 -0
- /reconcile/{test/saas_auto_promotions_manager/utils → cluster_auth_rhidp}/__init__.py +0 -0
- /reconcile/{test/saas_auto_promotions_manager/utils/saas_files_inventory → dynatrace_token_provider}/__init__.py +0 -0
- {release → reconcile/endpoints_discovery}/__init__.py +0 -0
- {tools/test → reconcile/external_resources}/__init__.py +0 -0
@@ -5,22 +5,22 @@ import json
|
|
5
5
|
import logging
|
6
6
|
import os
|
7
7
|
import re
|
8
|
-
from collections import
|
8
|
+
from collections import (
|
9
|
+
ChainMap,
|
10
|
+
defaultdict,
|
11
|
+
)
|
9
12
|
from collections.abc import (
|
13
|
+
Generator,
|
10
14
|
Iterable,
|
11
15
|
Mapping,
|
12
16
|
MutableMapping,
|
13
17
|
Sequence,
|
14
18
|
)
|
15
19
|
from contextlib import suppress
|
20
|
+
from datetime import UTC, datetime, timedelta
|
16
21
|
from types import TracebackType
|
17
|
-
from typing import
|
18
|
-
|
19
|
-
Generator,
|
20
|
-
Optional,
|
21
|
-
Type,
|
22
|
-
Union,
|
23
|
-
)
|
22
|
+
from typing import Any
|
23
|
+
from urllib.parse import urlparse
|
24
24
|
|
25
25
|
import yaml
|
26
26
|
from github import (
|
@@ -39,10 +39,10 @@ from sretoolbox.utils import (
|
|
39
39
|
|
40
40
|
from reconcile.github_org import get_default_config
|
41
41
|
from reconcile.status import RunningState
|
42
|
+
from reconcile.utils import helm
|
42
43
|
from reconcile.utils.gitlab_api import GitLabApi
|
43
44
|
from reconcile.utils.jenkins_api import JenkinsApi
|
44
45
|
from reconcile.utils.jjb_client import JJB
|
45
|
-
from reconcile.utils.mr.base import MRClient
|
46
46
|
from reconcile.utils.oc import (
|
47
47
|
OCLocal,
|
48
48
|
StatusCodeError,
|
@@ -51,6 +51,7 @@ from reconcile.utils.openshift_resource import OpenshiftResource as OR
|
|
51
51
|
from reconcile.utils.openshift_resource import (
|
52
52
|
ResourceInventory,
|
53
53
|
ResourceKeyExistsError,
|
54
|
+
ResourceNotManagedError,
|
54
55
|
fully_qualified_kind,
|
55
56
|
)
|
56
57
|
from reconcile.utils.promotion_state import (
|
@@ -58,8 +59,6 @@ from reconcile.utils.promotion_state import (
|
|
58
59
|
PromotionState,
|
59
60
|
)
|
60
61
|
from reconcile.utils.saasherder.interfaces import (
|
61
|
-
HasParameters,
|
62
|
-
HasSecretParameters,
|
63
62
|
SaasFile,
|
64
63
|
SaasParentSaasPromotion,
|
65
64
|
SaasResourceTemplate,
|
@@ -69,6 +68,7 @@ from reconcile.utils.saasherder.interfaces import (
|
|
69
68
|
SaasSecretParameters,
|
70
69
|
)
|
71
70
|
from reconcile.utils.saasherder.models import (
|
71
|
+
Channel,
|
72
72
|
ImageAuth,
|
73
73
|
Namespace,
|
74
74
|
Promotion,
|
@@ -87,7 +87,8 @@ from reconcile.utils.state import State
|
|
87
87
|
TARGET_CONFIG_HASH = "target_config_hash"
|
88
88
|
|
89
89
|
|
90
|
-
UNIQUE_SAAS_FILE_ENV_COMBO_LEN =
|
90
|
+
UNIQUE_SAAS_FILE_ENV_COMBO_LEN = 56
|
91
|
+
REQUEST_TIMEOUT = 60
|
91
92
|
|
92
93
|
|
93
94
|
def is_commit_sha(ref: str) -> bool:
|
@@ -95,7 +96,8 @@ def is_commit_sha(ref: str) -> bool:
|
|
95
96
|
return bool(re.search(r"^[0-9a-f]{40}$", ref))
|
96
97
|
|
97
98
|
|
98
|
-
|
99
|
+
# saas_name, resource_template_name, resource_template_url, target_uid
|
100
|
+
RtRef = tuple[str, str, str, str]
|
99
101
|
Resource = dict[str, Any]
|
100
102
|
Resources = list[Resource]
|
101
103
|
|
@@ -112,15 +114,17 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
112
114
|
secret_reader: SecretReaderBase,
|
113
115
|
hash_length: int,
|
114
116
|
repo_url: str,
|
115
|
-
gitlab:
|
116
|
-
jenkins_map:
|
117
|
-
state:
|
117
|
+
gitlab: GitLabApi | None = None,
|
118
|
+
jenkins_map: dict[str, JenkinsApi] | None = None,
|
119
|
+
state: State | None = None,
|
118
120
|
validate: bool = False,
|
119
121
|
include_trigger_trace: bool = False,
|
122
|
+
all_saas_files: Iterable[SaasFile] | None = None,
|
120
123
|
):
|
121
124
|
self.error_registered = False
|
122
125
|
self.saas_files = saas_files
|
123
126
|
self.repo_urls = self._collect_repo_urls()
|
127
|
+
self.image_patterns = self._collect_image_patterns()
|
124
128
|
self.resolve_templated_parameters(self.saas_files)
|
125
129
|
if validate:
|
126
130
|
self._validate_saas_files()
|
@@ -138,6 +142,10 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
138
142
|
self.include_trigger_trace = include_trigger_trace
|
139
143
|
self.state = state
|
140
144
|
self._promotion_state = PromotionState(state=state) if state else None
|
145
|
+
self._channel_map = self._assemble_channels(saas_files=all_saas_files)
|
146
|
+
self.images: set[str] = set()
|
147
|
+
self.blocked_versions = self._collect_blocked_versions()
|
148
|
+
self.hotfix_versions = self._collect_hotfix_versions()
|
141
149
|
|
142
150
|
# each namespace is in fact a target,
|
143
151
|
# so we can use it to calculate.
|
@@ -151,15 +159,18 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
151
159
|
self.compare = self._get_saas_file_feature_enabled("compare", default=True)
|
152
160
|
self.publish_job_logs = self._get_saas_file_feature_enabled("publish_job_logs")
|
153
161
|
self.cluster_admin = self._get_saas_file_feature_enabled("cluster_admin")
|
162
|
+
self.validate_planned_data = self._get_saas_file_feature_enabled(
|
163
|
+
"validate_planned_data", default=True
|
164
|
+
)
|
154
165
|
|
155
166
|
def __enter__(self) -> "SaasHerder":
|
156
167
|
return self
|
157
168
|
|
158
169
|
def __exit__(
|
159
170
|
self,
|
160
|
-
exc_type:
|
161
|
-
exc_value:
|
162
|
-
traceback:
|
171
|
+
exc_type: type[BaseException] | None,
|
172
|
+
exc_value: BaseException | None,
|
173
|
+
traceback: TracebackType | None,
|
163
174
|
) -> None:
|
164
175
|
self.cleanup()
|
165
176
|
|
@@ -189,8 +200,8 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
189
200
|
yield (saas_file, resource_template, target)
|
190
201
|
|
191
202
|
def _get_saas_file_feature_enabled(
|
192
|
-
self, name: str, default:
|
193
|
-
) ->
|
203
|
+
self, name: str, default: bool | None = None
|
204
|
+
) -> bool | None:
|
194
205
|
"""Returns a bool indicating if a feature is enabled in a saas file,
|
195
206
|
or a supplied default. Returns False if there are multiple
|
196
207
|
saas files in the process.
|
@@ -231,26 +242,34 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
231
242
|
f"secret parameter path '{sp.secret.path}' does not match any of allowedSecretParameterPaths"
|
232
243
|
)
|
233
244
|
|
245
|
+
def _validate_target_in_app(
|
246
|
+
self, saas_file: SaasFile, target: SaasResourceTemplateTarget
|
247
|
+
) -> None:
|
248
|
+
if saas_file.validate_targets_in_app:
|
249
|
+
valid_app_names = {saas_file.app.name}
|
250
|
+
if saas_file.app.parent_app:
|
251
|
+
valid_app_names.add(saas_file.app.parent_app.name)
|
252
|
+
if target.namespace.app.name not in valid_app_names:
|
253
|
+
logging.error(
|
254
|
+
f"[{saas_file.name}] all targets (i.e., namespaces that you deploy to) within the saas file '{saas_file.name}' must belong to app(s) {valid_app_names}. However, your saas file also deploys to namespace '{target.namespace.name}' which belongs to app '{target.namespace.app.name}'."
|
255
|
+
)
|
256
|
+
self.valid = False
|
257
|
+
|
234
258
|
def _validate_saas_files(self) -> None:
|
235
259
|
self.valid = True
|
236
260
|
saas_file_name_path_map: dict[str, list[str]] = {}
|
237
261
|
tkn_unique_pipelineruns: dict[str, str] = {}
|
238
262
|
|
239
|
-
publications: dict[str, RtRef] =
|
240
|
-
subscriptions: dict[str, list[RtRef]] =
|
263
|
+
publications: dict[str, set[RtRef]] = defaultdict(set)
|
264
|
+
subscriptions: dict[str, list[RtRef]] = defaultdict(list)
|
241
265
|
|
242
266
|
for saas_file in self.saas_files:
|
243
267
|
saas_file_name_path_map.setdefault(saas_file.name, [])
|
244
268
|
saas_file_name_path_map[saas_file.name].append(saas_file.path)
|
245
269
|
|
246
|
-
|
247
|
-
u.org_username
|
248
|
-
for r in saas_file.self_service_roles or []
|
249
|
-
for u in list(r.users) + list(r.bots)
|
250
|
-
]
|
251
|
-
if not saas_file_owners:
|
270
|
+
if not saas_file.app.self_service_roles:
|
252
271
|
logging.error(
|
253
|
-
f"
|
272
|
+
f"app {saas_file.app.name} has no self-service roles (saas file {saas_file.name})"
|
254
273
|
)
|
255
274
|
self.valid = False
|
256
275
|
|
@@ -309,12 +328,17 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
309
328
|
target.namespace.environment.secret_parameters or [],
|
310
329
|
saas_file.allowed_secret_parameter_paths or [],
|
311
330
|
)
|
331
|
+
self._validate_target_in_app(saas_file, target)
|
312
332
|
|
313
333
|
if target.promotion:
|
314
334
|
rt_ref = (
|
315
335
|
saas_file.path,
|
316
336
|
resource_template.name,
|
317
337
|
resource_template.url,
|
338
|
+
target.uid(
|
339
|
+
parent_saas_file_name=saas_file.name,
|
340
|
+
parent_resource_template_name=resource_template.name,
|
341
|
+
),
|
318
342
|
)
|
319
343
|
|
320
344
|
# Get publications and subscriptions for the target
|
@@ -395,7 +419,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
395
419
|
self,
|
396
420
|
rt_ref: RtRef,
|
397
421
|
promotion: SaasResourceTemplateTargetPromotion,
|
398
|
-
publications: MutableMapping[str, RtRef],
|
422
|
+
publications: MutableMapping[str, set[RtRef]],
|
399
423
|
subscriptions: MutableMapping[str, list[RtRef]],
|
400
424
|
) -> None:
|
401
425
|
"""
|
@@ -403,23 +427,23 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
403
427
|
It validates a publish channel is unique across all publish targets.
|
404
428
|
"""
|
405
429
|
for channel in promotion.publish or []:
|
406
|
-
if
|
430
|
+
if rt_ref in publications[channel]:
|
407
431
|
self.valid = False
|
432
|
+
# This should never be possible theoretically ...
|
408
433
|
logging.error(
|
409
|
-
"
|
410
|
-
"
|
434
|
+
f"Non-unique resource template reference {rt_ref} in "
|
435
|
+
f"channel {channel}"
|
411
436
|
)
|
412
437
|
continue
|
413
|
-
publications[channel]
|
438
|
+
publications[channel].add(rt_ref)
|
414
439
|
|
415
440
|
for channel in promotion.subscribe or []:
|
416
|
-
subscriptions.setdefault(channel, [])
|
417
441
|
subscriptions[channel].append(rt_ref)
|
418
442
|
|
419
443
|
def _check_promotions_have_same_source(
|
420
444
|
self,
|
421
445
|
subscriptions: Mapping[str, list[RtRef]],
|
422
|
-
publications: Mapping[str, RtRef],
|
446
|
+
publications: Mapping[str, set[RtRef]],
|
423
447
|
) -> None:
|
424
448
|
"""
|
425
449
|
Function to check that a promotion has the same repository
|
@@ -427,52 +451,58 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
427
451
|
"""
|
428
452
|
|
429
453
|
for sub_channel, sub_targets in subscriptions.items():
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
(pub_saas, pub_rt_name, pub_rt_url) = pub_channel_ref
|
435
|
-
|
436
|
-
for sub_saas, sub_rt_name, sub_rt_url in sub_targets:
|
437
|
-
if not pub_channel_ref:
|
454
|
+
pub_channel_refs = publications.get(sub_channel, set())
|
455
|
+
for sub_saas, sub_rt_name, sub_rt_url, _ in sub_targets:
|
456
|
+
if not pub_channel_refs:
|
457
|
+
self.valid = False
|
438
458
|
logging.error(
|
439
459
|
"Channel is not published by any target\n"
|
440
|
-
"subscriber_saas: {}\n"
|
441
|
-
"subscriber_rt: {}\n"
|
442
|
-
"channel: {}"
|
460
|
+
f"subscriber_saas: {sub_saas}\n"
|
461
|
+
f"subscriber_rt: {sub_rt_name}\n"
|
462
|
+
f"channel: {sub_channel}"
|
443
463
|
)
|
444
|
-
|
464
|
+
for pub_ref in pub_channel_refs:
|
465
|
+
(pub_saas, pub_rt_name, pub_rt_url, _) = pub_ref
|
445
466
|
if sub_rt_url != pub_rt_url:
|
446
467
|
self.valid = False
|
447
468
|
logging.error(
|
448
469
|
"Subscriber and Publisher targets have different "
|
449
470
|
"source repositories\n"
|
450
|
-
"publisher_saas: {}\n"
|
451
|
-
"publisher_rt: {}\n"
|
452
|
-
"publisher_repo: {}\n"
|
453
|
-
"subscriber_saas: {}\n"
|
454
|
-
"subscriber_rt: {}\n"
|
455
|
-
"subscriber_repo: {}\n"
|
456
|
-
pub_saas,
|
457
|
-
pub_rt_name,
|
458
|
-
pub_rt_url,
|
459
|
-
sub_saas,
|
460
|
-
sub_rt_name,
|
461
|
-
sub_rt_url,
|
462
|
-
)
|
471
|
+
f"publisher_saas: {pub_saas}\n"
|
472
|
+
f"publisher_rt: {pub_rt_name}\n"
|
473
|
+
f"publisher_repo: {pub_rt_url}\n"
|
474
|
+
f"subscriber_saas: {sub_saas}\n"
|
475
|
+
f"subscriber_rt: {sub_rt_name}\n"
|
476
|
+
f"subscriber_repo: {sub_rt_url}\n"
|
463
477
|
)
|
464
478
|
|
479
|
+
@staticmethod
|
480
|
+
def build_saas_file_env_combo(
|
481
|
+
saas_file_name: str,
|
482
|
+
env_name: str,
|
483
|
+
) -> tuple[str, str]:
|
484
|
+
"""
|
485
|
+
Build a tuple of short and long names for a saas file and environment combo,
|
486
|
+
max tekton pipelinerun name length can be 63,
|
487
|
+
leaving 7 for the rerun leaves us with 56 to create a unique pipelinerun name.
|
488
|
+
|
489
|
+
:param saas_file_name: name of the saas file
|
490
|
+
:param env_name: name of the environment
|
491
|
+
:return: (tkn_name, tkn_long_name)
|
492
|
+
"""
|
493
|
+
tkn_long_name = f"{saas_file_name}-{env_name}"
|
494
|
+
tkn_name = tkn_long_name[:UNIQUE_SAAS_FILE_ENV_COMBO_LEN].rstrip("-")
|
495
|
+
return tkn_name, tkn_long_name
|
496
|
+
|
465
497
|
def _check_saas_file_env_combo_unique(
|
466
498
|
self,
|
467
499
|
saas_file_name: str,
|
468
500
|
env_name: str,
|
469
501
|
tkn_unique_pipelineruns: Mapping[str, str],
|
470
502
|
) -> tuple[str, str]:
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
tkn_long_name = f"{saas_file_name}-{env_name}"
|
475
|
-
tkn_name = tkn_long_name[:UNIQUE_SAAS_FILE_ENV_COMBO_LEN]
|
503
|
+
tkn_name, tkn_long_name = self.build_saas_file_env_combo(
|
504
|
+
saas_file_name, env_name
|
505
|
+
)
|
476
506
|
if (
|
477
507
|
tkn_name in tkn_unique_pipelineruns
|
478
508
|
and tkn_unique_pipelineruns[tkn_name] != tkn_long_name
|
@@ -663,43 +693,24 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
663
693
|
environment=target.namespace.environment,
|
664
694
|
app=target.namespace.app,
|
665
695
|
cluster=target.namespace.cluster,
|
666
|
-
# managedResourceTypes
|
667
|
-
# add
|
696
|
+
# managedResourceTypes and managedResourceNames are defined per saas_file
|
697
|
+
# add them to each namespace in the current saas_file
|
668
698
|
managed_resource_types=saas_file.managed_resource_types,
|
699
|
+
managed_resource_names=saas_file.managed_resource_names,
|
669
700
|
)
|
670
701
|
)
|
671
702
|
return namespaces
|
672
703
|
|
673
704
|
def _collect_repo_urls(self) -> set[str]:
|
674
|
-
return
|
705
|
+
return {
|
675
706
|
rt.url
|
676
707
|
for saas_file in self.saas_files
|
677
708
|
for rt in saas_file.resource_templates
|
678
|
-
)
|
679
|
-
|
680
|
-
@staticmethod
|
681
|
-
def _collect_parameters(container: HasParameters) -> dict[str, str]:
|
682
|
-
parameters = container.parameters or {}
|
683
|
-
if isinstance(parameters, str):
|
684
|
-
parameters = json.loads(parameters)
|
685
|
-
# adjust Python's True/False
|
686
|
-
for k, v in parameters.items():
|
687
|
-
if v is True:
|
688
|
-
parameters[k] = "true"
|
689
|
-
elif v is False:
|
690
|
-
parameters[k] = "false"
|
691
|
-
elif any(isinstance(v, t) for t in [dict, list, tuple]):
|
692
|
-
parameters[k] = json.dumps(v)
|
693
|
-
return parameters
|
694
|
-
|
695
|
-
def _collect_secret_parameters(
|
696
|
-
self, container: HasSecretParameters
|
697
|
-
) -> dict[str, str]:
|
698
|
-
return {
|
699
|
-
sp.name: self.secret_reader.read_secret(sp.secret)
|
700
|
-
for sp in container.secret_parameters or []
|
701
709
|
}
|
702
710
|
|
711
|
+
def _collect_image_patterns(self) -> set[str]:
|
712
|
+
return {p for sf in self.saas_files for p in sf.image_patterns}
|
713
|
+
|
703
714
|
@staticmethod
|
704
715
|
def _get_file_contents_github(repo: Repository, path: str, commit_sha: str) -> str:
|
705
716
|
f = repo.get_contents(path, commit_sha)
|
@@ -718,11 +729,31 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
718
729
|
|
719
730
|
return ""
|
720
731
|
|
732
|
+
@retry(max_attempts=20)
|
733
|
+
def get_archive_info(
|
734
|
+
self,
|
735
|
+
saas_file: SaasFile,
|
736
|
+
trigger_reason: str,
|
737
|
+
) -> tuple[str, str]:
|
738
|
+
[url, sha] = trigger_reason.split(" ")[0].split("/commit/")
|
739
|
+
repo_name = urlparse(url).path.strip("/")
|
740
|
+
file_name = f"{repo_name.replace('/', '-')}-{sha}.tar.gz"
|
741
|
+
if "github" in url:
|
742
|
+
github = self._initiate_github(saas_file, base_url="https://api.github.com")
|
743
|
+
repo = github.get_repo(repo_name)
|
744
|
+
# get_archive_link get redirect url form header, it does not work with github-mirror
|
745
|
+
archive_url = repo.get_archive_link("tarball", ref=sha)
|
746
|
+
elif "gitlab" in url:
|
747
|
+
archive_url = f"{url}/-/archive/{sha}/{file_name}"
|
748
|
+
else:
|
749
|
+
raise Exception(f"Only GitHub and GitLab are supported: {url}")
|
750
|
+
|
751
|
+
return file_name, archive_url
|
752
|
+
|
721
753
|
@retry(max_attempts=20)
|
722
754
|
def _get_file_contents(
|
723
755
|
self, url: str, path: str, ref: str, github: Github
|
724
|
-
) -> tuple[Any, str
|
725
|
-
html_url = f"{url}/blob/{ref}{path}"
|
756
|
+
) -> tuple[Any, str]:
|
726
757
|
commit_sha = self._get_commit_sha(url, ref, github)
|
727
758
|
|
728
759
|
if "github" in url:
|
@@ -738,15 +769,14 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
738
769
|
else:
|
739
770
|
raise Exception(f"Only GitHub and GitLab are supported: {url}")
|
740
771
|
|
741
|
-
return yaml.safe_load(content),
|
772
|
+
return yaml.safe_load(content), commit_sha
|
742
773
|
|
743
774
|
@retry()
|
744
775
|
def _get_directory_contents(
|
745
776
|
self, url: str, path: str, ref: str, github: Github
|
746
|
-
) -> tuple[list[Any], str
|
747
|
-
html_url = f"{url}/tree/{ref}{path}"
|
777
|
+
) -> tuple[list[Any], str]:
|
748
778
|
commit_sha = self._get_commit_sha(url, ref, github)
|
749
|
-
resources = []
|
779
|
+
resources: list[Any] = []
|
750
780
|
if "github" in url:
|
751
781
|
repo_name = url.rstrip("/").replace("https://github.com/", "")
|
752
782
|
repo = github.get_repo(repo_name)
|
@@ -758,8 +788,8 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
758
788
|
file_contents_decoded = self._get_file_contents_github(
|
759
789
|
repo, file_path, commit_sha
|
760
790
|
)
|
761
|
-
|
762
|
-
resources.
|
791
|
+
result_resources = yaml.safe_load_all(file_contents_decoded)
|
792
|
+
resources.extend(result_resources)
|
763
793
|
elif "gitlab" in url:
|
764
794
|
if not self.gitlab:
|
765
795
|
raise Exception("gitlab is not initialized")
|
@@ -775,7 +805,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
775
805
|
else:
|
776
806
|
raise Exception(f"Only GitHub and GitLab are supported: {url}")
|
777
807
|
|
778
|
-
return resources,
|
808
|
+
return resources, commit_sha
|
779
809
|
|
780
810
|
@retry()
|
781
811
|
def _get_commit_sha(self, url: str, ref: str, github: Github) -> str:
|
@@ -789,7 +819,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
789
819
|
if not self.gitlab:
|
790
820
|
raise Exception("gitlab is not initialized")
|
791
821
|
project = self.gitlab.get_project(url)
|
792
|
-
commits = project.commits.list(ref_name=ref)
|
822
|
+
commits = project.commits.list(ref_name=ref, per_page=1, page=1)
|
793
823
|
commit_sha = commits[0].id
|
794
824
|
|
795
825
|
return commit_sha
|
@@ -838,60 +868,32 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
838
868
|
return True
|
839
869
|
return False
|
840
870
|
|
841
|
-
def _process_template(
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
target_config_hash: str,
|
854
|
-
) -> tuple[list[Any], str, Optional[Promotion]]:
|
855
|
-
if provider == "openshift-template":
|
856
|
-
environment_parameters = self._collect_parameters(
|
857
|
-
target.namespace.environment
|
858
|
-
)
|
859
|
-
environment_secret_parameters = self._collect_secret_parameters(
|
860
|
-
target.namespace.environment
|
861
|
-
)
|
862
|
-
target_parameters = self._collect_parameters(target)
|
863
|
-
target_secret_parameters = self._collect_secret_parameters(target)
|
864
|
-
|
865
|
-
consolidated_parameters = {}
|
866
|
-
consolidated_parameters.update(environment_parameters)
|
867
|
-
consolidated_parameters.update(environment_secret_parameters)
|
868
|
-
consolidated_parameters.update(parameters)
|
869
|
-
consolidated_parameters.update(target_parameters)
|
870
|
-
consolidated_parameters.update(target_secret_parameters)
|
871
|
-
|
872
|
-
for replace_key, replace_value in consolidated_parameters.items():
|
873
|
-
if not isinstance(replace_value, str):
|
874
|
-
continue
|
875
|
-
replace_pattern = "${" + replace_key + "}"
|
876
|
-
for k, v in consolidated_parameters.items():
|
877
|
-
if not isinstance(v, str):
|
878
|
-
continue
|
879
|
-
if replace_pattern in v:
|
880
|
-
consolidated_parameters[k] = v.replace(
|
881
|
-
replace_pattern, replace_value
|
882
|
-
)
|
871
|
+
def _process_template(self, spec: TargetSpec) -> tuple[list[Any], Promotion | None]:
|
872
|
+
saas_file_name = spec.saas_file_name
|
873
|
+
resource_template_name = spec.resource_template_name
|
874
|
+
url = spec.url
|
875
|
+
path = spec.path
|
876
|
+
ref = spec.ref
|
877
|
+
provider = spec.provider
|
878
|
+
hash_length = spec.hash_length
|
879
|
+
target = spec.target
|
880
|
+
github = spec.github
|
881
|
+
target_config_hash = spec.target_config_hash
|
882
|
+
error_prefix = spec.error_prefix
|
883
883
|
|
884
|
+
if provider == "openshift-template":
|
885
|
+
consolidated_parameters = spec.parameters()
|
884
886
|
try:
|
885
|
-
template,
|
886
|
-
url=url, path=path, ref=
|
887
|
+
template, commit_sha = self._get_file_contents(
|
888
|
+
url=url, path=path, ref=ref, github=github
|
887
889
|
)
|
888
890
|
except Exception as e:
|
889
|
-
logging.error(
|
890
|
-
f"[{url}/blob/{target.ref}{path}] "
|
891
|
-
+ f"error fetching template: {str(e)}"
|
892
|
-
)
|
891
|
+
logging.error(f"{error_prefix} error fetching template: {e!s}")
|
893
892
|
raise
|
894
893
|
|
894
|
+
# add COMMIT_SHA only if it is unspecified
|
895
|
+
consolidated_parameters.setdefault("COMMIT_SHA", commit_sha)
|
896
|
+
|
895
897
|
# add IMAGE_TAG only if it is unspecified
|
896
898
|
if not (image_tag := consolidated_parameters.get("IMAGE_TAG", "")):
|
897
899
|
sha_substring = commit_sha[:hash_length]
|
@@ -904,8 +906,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
904
906
|
channel = consolidated_parameters["CHANNEL"]
|
905
907
|
except KeyError:
|
906
908
|
logging.error(
|
907
|
-
f"
|
908
|
-
+ f"{html_url}: CHANNEL is required when "
|
909
|
+
f"{error_prefix} CHANNEL is required when "
|
909
910
|
+ "'use_channel_in_image_tag' is true."
|
910
911
|
)
|
911
912
|
raise
|
@@ -927,71 +928,129 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
927
928
|
registry_image = consolidated_parameters["REGISTRY_IMG"]
|
928
929
|
except KeyError as e:
|
929
930
|
logging.error(
|
930
|
-
f"
|
931
|
-
+ f"{html_url}: error generating REPO_DIGEST. "
|
931
|
+
f"{error_prefix} error generating REPO_DIGEST. "
|
932
932
|
+ "Is REGISTRY_IMG missing? "
|
933
|
-
+ f"{
|
934
|
-
)
|
935
|
-
raise
|
936
|
-
try:
|
937
|
-
image_uri = f"{registry_image}:{image_tag}"
|
938
|
-
img = Image(
|
939
|
-
url=image_uri,
|
940
|
-
username=image_auth.username,
|
941
|
-
password=image_auth.password,
|
942
|
-
auth_server=image_auth.auth_server,
|
943
|
-
)
|
944
|
-
if need_repo_digest:
|
945
|
-
consolidated_parameters["REPO_DIGEST"] = img.url_digest
|
946
|
-
if need_image_digest:
|
947
|
-
consolidated_parameters["IMAGE_DIGEST"] = img.digest
|
948
|
-
except (rqexc.ConnectionError, rqexc.HTTPError) as e:
|
949
|
-
logging.error(
|
950
|
-
f"[{saas_file_name}/{resource_template_name}] "
|
951
|
-
+ f"{html_url}: error generating REPO_DIGEST for "
|
952
|
-
+ f"{image_uri}: {str(e)}"
|
933
|
+
+ f"{e!s}"
|
953
934
|
)
|
954
935
|
raise
|
955
936
|
|
937
|
+
image_uri = f"{registry_image}:{image_tag}"
|
938
|
+
img = self._get_image(
|
939
|
+
image=image_uri,
|
940
|
+
image_patterns=spec.image_patterns,
|
941
|
+
image_auth=spec.image_auth,
|
942
|
+
error_prefix=error_prefix,
|
943
|
+
)
|
944
|
+
if not img:
|
945
|
+
msg = f"{error_prefix} error get image for {image_uri}"
|
946
|
+
logging.error(msg)
|
947
|
+
raise Exception(msg)
|
948
|
+
|
949
|
+
if need_repo_digest:
|
950
|
+
consolidated_parameters["REPO_DIGEST"] = img.url_digest
|
951
|
+
if need_image_digest:
|
952
|
+
consolidated_parameters["IMAGE_DIGEST"] = img.digest
|
953
|
+
|
956
954
|
oc = OCLocal("cluster", None, None, local=True)
|
957
955
|
try:
|
958
956
|
resources = oc.process(template, consolidated_parameters)
|
959
957
|
except StatusCodeError as e:
|
960
|
-
logging.error(
|
961
|
-
f"[{saas_file_name}/{resource_template_name}] "
|
962
|
-
+ f"{html_url}: error processing template: {str(e)}"
|
963
|
-
)
|
958
|
+
logging.error(f"{error_prefix} error processing template: {e!s}")
|
964
959
|
|
965
960
|
elif provider == "directory":
|
966
961
|
try:
|
967
|
-
resources,
|
968
|
-
url=url, path=path, ref=
|
962
|
+
resources, commit_sha = self._get_directory_contents(
|
963
|
+
url=url, path=path, ref=ref, github=github
|
969
964
|
)
|
970
965
|
except Exception as e:
|
971
966
|
logging.error(
|
972
|
-
f"
|
973
|
-
+
|
967
|
+
f"{error_prefix} error fetching directory: {e!s} "
|
968
|
+
+ "(We do not support nested directories. Do you by chance have subdirectories?)"
|
974
969
|
)
|
975
970
|
raise
|
976
971
|
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
972
|
+
elif provider == "helm":
|
973
|
+
ssl_verify = (
|
974
|
+
self.gitlab.ssl_verify
|
975
|
+
if self.gitlab and url.startswith(self.gitlab.server)
|
976
|
+
else True
|
977
|
+
)
|
978
|
+
consolidated_parameters = spec.parameters(adjust=False)
|
979
|
+
resources = helm.template_all(
|
980
|
+
url=url,
|
981
|
+
path=path,
|
982
|
+
ref=ref,
|
983
|
+
namespace=spec.target.namespace.name,
|
984
|
+
values=consolidated_parameters,
|
985
|
+
ssl_verify=ssl_verify,
|
981
986
|
)
|
982
987
|
|
988
|
+
else:
|
989
|
+
logging.error(f"{error_prefix} unknown provider: {provider}")
|
990
|
+
|
983
991
|
target_promotion = None
|
984
992
|
if target.promotion:
|
993
|
+
channels = [
|
994
|
+
self._channel_map[sub] for sub in target.promotion.subscribe or []
|
995
|
+
]
|
985
996
|
target_promotion = Promotion(
|
997
|
+
url=url,
|
986
998
|
auto=target.promotion.auto,
|
987
999
|
publish=target.promotion.publish,
|
988
|
-
subscribe=
|
1000
|
+
subscribe=channels,
|
989
1001
|
promotion_data=target.promotion.promotion_data,
|
990
1002
|
commit_sha=commit_sha,
|
991
1003
|
saas_file=saas_file_name,
|
992
1004
|
target_config_hash=target_config_hash,
|
1005
|
+
saas_target_uid=target.uid(
|
1006
|
+
parent_resource_template_name=resource_template_name,
|
1007
|
+
parent_saas_file_name=saas_file_name,
|
1008
|
+
),
|
1009
|
+
soak_days=target.promotion.soak_days or 0,
|
993
1010
|
)
|
994
|
-
return resources,
|
1011
|
+
return resources, target_promotion
|
1012
|
+
|
1013
|
+
def _assemble_channels(
|
1014
|
+
self, saas_files: Iterable[SaasFile] | None
|
1015
|
+
) -> dict[str, Channel]:
|
1016
|
+
"""
|
1017
|
+
We need to assemble all publisher_uids that are publishing to a channel.
|
1018
|
+
These uids are required to validate correctness of promotions.
|
1019
|
+
"""
|
1020
|
+
channel_map: dict[str, Channel] = {}
|
1021
|
+
for saas_file in saas_files or []:
|
1022
|
+
for tmpl in saas_file.resource_templates:
|
1023
|
+
for target in tmpl.targets:
|
1024
|
+
if not target.promotion:
|
1025
|
+
continue
|
1026
|
+
for publish in target.promotion.publish or []:
|
1027
|
+
publisher_uid = target.uid(
|
1028
|
+
parent_saas_file_name=saas_file.name,
|
1029
|
+
parent_resource_template_name=tmpl.name,
|
1030
|
+
)
|
1031
|
+
if publish not in channel_map:
|
1032
|
+
channel_map[publish] = Channel(
|
1033
|
+
name=publish,
|
1034
|
+
publisher_uids=[],
|
1035
|
+
)
|
1036
|
+
channel_map[publish].publisher_uids.append(publisher_uid)
|
1037
|
+
return channel_map
|
1038
|
+
|
1039
|
+
def _collect_blocked_versions(self) -> dict[str, set[str]]:
|
1040
|
+
blocked_versions: dict[str, set[str]] = {}
|
1041
|
+
for saas_file in self.saas_files:
|
1042
|
+
for cc in saas_file.app.code_components or []:
|
1043
|
+
for v in cc.blocked_versions or []:
|
1044
|
+
blocked_versions.setdefault(cc.url, set()).add(v)
|
1045
|
+
return blocked_versions
|
1046
|
+
|
1047
|
+
def _collect_hotfix_versions(self) -> dict[str, set[str]]:
|
1048
|
+
hotfix_versions: dict[str, set[str]] = {}
|
1049
|
+
for saas_file in self.saas_files:
|
1050
|
+
for cc in saas_file.app.code_components or []:
|
1051
|
+
for v in cc.hotfix_versions or []:
|
1052
|
+
hotfix_versions.setdefault(cc.url, set()).add(v)
|
1053
|
+
return hotfix_versions
|
995
1054
|
|
996
1055
|
@staticmethod
|
997
1056
|
def _collect_images(resource: Resource) -> set[str]:
|
@@ -1028,79 +1087,114 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1028
1087
|
return images
|
1029
1088
|
|
1030
1089
|
@staticmethod
|
1031
|
-
def
|
1090
|
+
def _get_image(
|
1032
1091
|
image: str,
|
1033
1092
|
image_patterns: Iterable[str],
|
1034
1093
|
image_auth: ImageAuth,
|
1035
1094
|
error_prefix: str,
|
1036
|
-
) ->
|
1037
|
-
error = False
|
1095
|
+
) -> Image | None:
|
1038
1096
|
if not image_patterns:
|
1039
|
-
error = True
|
1040
1097
|
logging.error(
|
1041
1098
|
f"{error_prefix} imagePatterns is empty (does not contain {image})"
|
1042
1099
|
)
|
1100
|
+
return None
|
1043
1101
|
if image_patterns and not any(image.startswith(p) for p in image_patterns):
|
1044
|
-
error = True
|
1045
1102
|
logging.error(f"{error_prefix} Image is not in imagePatterns: {image}")
|
1103
|
+
return None
|
1104
|
+
|
1105
|
+
# .dockerconfigjson
|
1106
|
+
if image_auth.docker_config:
|
1107
|
+
# we rely on the secret in vault being ordered
|
1108
|
+
# https://peps.python.org/pep-0468/
|
1109
|
+
for registry, auth in image_auth.docker_config["auths"].items():
|
1110
|
+
if not image.startswith(registry):
|
1111
|
+
continue
|
1112
|
+
username, password = (
|
1113
|
+
base64.b64decode(auth["auth"]).decode("utf-8").split(":")
|
1114
|
+
)
|
1115
|
+
|
1116
|
+
return SaasHerder._get_and_validate_image(
|
1117
|
+
full_image_path=image,
|
1118
|
+
username=username,
|
1119
|
+
password=password,
|
1120
|
+
auth_server=image_auth.auth_server,
|
1121
|
+
timeout=REQUEST_TIMEOUT,
|
1122
|
+
error_prefix=error_prefix,
|
1123
|
+
)
|
1124
|
+
|
1125
|
+
# basic auth fallback for backwards compatibility
|
1126
|
+
return SaasHerder._get_and_validate_image(
|
1127
|
+
full_image_path=image,
|
1128
|
+
username=image_auth.username,
|
1129
|
+
password=image_auth.password,
|
1130
|
+
auth_server=image_auth.auth_server,
|
1131
|
+
timeout=REQUEST_TIMEOUT,
|
1132
|
+
error_prefix=error_prefix,
|
1133
|
+
)
|
1134
|
+
|
1135
|
+
@staticmethod
|
1136
|
+
def _get_and_validate_image(
|
1137
|
+
full_image_path: str,
|
1138
|
+
username: str | Any,
|
1139
|
+
password: str | Any,
|
1140
|
+
auth_server: str | Any,
|
1141
|
+
timeout: int,
|
1142
|
+
error_prefix: str,
|
1143
|
+
) -> Image | None:
|
1046
1144
|
try:
|
1047
|
-
|
1048
|
-
|
1049
|
-
username=
|
1050
|
-
password=
|
1051
|
-
auth_server=
|
1145
|
+
img = Image(
|
1146
|
+
full_image_path,
|
1147
|
+
username=username,
|
1148
|
+
password=password,
|
1149
|
+
auth_server=auth_server,
|
1150
|
+
timeout=timeout,
|
1052
1151
|
)
|
1053
|
-
if
|
1054
|
-
|
1055
|
-
|
1152
|
+
if img:
|
1153
|
+
return img
|
1154
|
+
else:
|
1155
|
+
logging.error(
|
1156
|
+
f"{error_prefix} Image : {full_image_path} does not exist"
|
1157
|
+
)
|
1056
1158
|
except Exception as e:
|
1057
|
-
error = True
|
1058
1159
|
logging.error(
|
1059
|
-
f"{error_prefix} Image is invalid: {
|
1160
|
+
f"{error_prefix} Image is invalid: {full_image_path}. "
|
1161
|
+
+ f"details: {e!s}"
|
1060
1162
|
)
|
1061
|
-
|
1062
|
-
return error
|
1163
|
+
return None
|
1063
1164
|
|
1064
1165
|
def _check_images(
|
1065
1166
|
self,
|
1066
|
-
|
1067
|
-
resource_template_name: str,
|
1068
|
-
image_auth: ImageAuth,
|
1069
|
-
image_patterns: list[str],
|
1070
|
-
html_url: str,
|
1167
|
+
spec: TargetSpec,
|
1071
1168
|
resources: Resources,
|
1072
1169
|
) -> bool:
|
1073
|
-
error_prefix = f"[{saas_file_name}/{resource_template_name}] {html_url}:"
|
1074
1170
|
images_list = threaded.run(
|
1075
1171
|
self._collect_images, resources, self.available_thread_pool_size
|
1076
1172
|
)
|
1077
1173
|
images = set(itertools.chain.from_iterable(images_list))
|
1174
|
+
self.images.update(images)
|
1078
1175
|
if not images:
|
1079
1176
|
return False # no errors
|
1080
|
-
|
1081
|
-
self.
|
1177
|
+
images = threaded.run(
|
1178
|
+
self._get_image,
|
1082
1179
|
images,
|
1083
1180
|
self.available_thread_pool_size,
|
1084
|
-
image_patterns=image_patterns,
|
1085
|
-
image_auth=image_auth,
|
1086
|
-
error_prefix=error_prefix,
|
1181
|
+
image_patterns=spec.image_patterns,
|
1182
|
+
image_auth=spec.image_auth,
|
1183
|
+
error_prefix=spec.error_prefix,
|
1087
1184
|
)
|
1088
|
-
return
|
1185
|
+
return None in images
|
1089
1186
|
|
1090
|
-
def _initiate_github(
|
1187
|
+
def _initiate_github(
|
1188
|
+
self, saas_file: SaasFile, base_url: str | None = None
|
1189
|
+
) -> Github:
|
1091
1190
|
token = (
|
1092
1191
|
self.secret_reader.read_secret(saas_file.authentication.code)
|
1093
1192
|
if saas_file.authentication and saas_file.authentication.code
|
1094
1193
|
else get_default_config()["token"]
|
1095
1194
|
)
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
# connections pool to live in that world
|
1100
|
-
# (this avoids the warning "Connection pool is
|
1101
|
-
# full, discarding connection: api.github.com")
|
1102
|
-
pool_size = 100
|
1103
|
-
return Github(token, base_url=base_url, pool_size=pool_size)
|
1195
|
+
if not base_url:
|
1196
|
+
base_url = os.environ.get("GITHUB_API", "https://api.github.com")
|
1197
|
+
return Github(token, base_url=base_url)
|
1104
1198
|
|
1105
1199
|
def _initiate_image_auth(self, saas_file: SaasFile) -> ImageAuth:
|
1106
1200
|
"""
|
@@ -1121,20 +1215,24 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1121
1215
|
return ImageAuth()
|
1122
1216
|
|
1123
1217
|
creds = self.secret_reader.read_all_secret(saas_file.authentication.image)
|
1124
|
-
|
1125
|
-
|
1218
|
+
required_docker_config_keys = [".dockerconfigjson"]
|
1219
|
+
required_keys_basic_auth = ["user", "token"]
|
1220
|
+
ok = all(k in creds for k in required_keys_basic_auth) or all(
|
1221
|
+
k in creds for k in required_docker_config_keys
|
1222
|
+
)
|
1126
1223
|
if not ok:
|
1127
1224
|
logging.warning(
|
1128
1225
|
"the specified image authentication secret "
|
1129
1226
|
+ f"found in path {saas_file.authentication.image.path} "
|
1130
|
-
+ f"does not contain all required keys: {
|
1227
|
+
+ f"does not contain all required keys: {required_docker_config_keys} or {required_keys_basic_auth}"
|
1131
1228
|
)
|
1132
1229
|
return ImageAuth()
|
1133
1230
|
|
1134
1231
|
return ImageAuth(
|
1135
|
-
username=creds
|
1136
|
-
password=creds
|
1232
|
+
username=creds.get("user"),
|
1233
|
+
password=creds.get("token"),
|
1137
1234
|
auth_server=creds.get("url"),
|
1235
|
+
docker_config=json.loads(creds.get(".dockerconfigjson") or "{}"),
|
1138
1236
|
)
|
1139
1237
|
|
1140
1238
|
def populate_desired_state(self, ri: ResourceInventory) -> None:
|
@@ -1150,7 +1248,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1150
1248
|
self.thread_pool_size,
|
1151
1249
|
ri=ri,
|
1152
1250
|
)
|
1153
|
-
self.promotions: list[
|
1251
|
+
self.promotions: list[Promotion | None] = promotions
|
1154
1252
|
|
1155
1253
|
def _init_populate_desired_state_specs(
|
1156
1254
|
self, saas_file: SaasFile
|
@@ -1158,23 +1256,10 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1158
1256
|
specs = []
|
1159
1257
|
github = self._initiate_github(saas_file)
|
1160
1258
|
image_auth = self._initiate_image_auth(saas_file)
|
1161
|
-
saas_file_parameters = self._collect_parameters(saas_file)
|
1162
|
-
saas_file_secret_parameters = self._collect_secret_parameters(saas_file)
|
1163
|
-
|
1164
1259
|
all_trigger_specs = self.get_saas_targets_config_trigger_specs(saas_file)
|
1165
1260
|
# iterate over resource templates (multiple per saas_file)
|
1166
1261
|
for rt in saas_file.resource_templates:
|
1167
|
-
provider = rt.provider or "openshift-template"
|
1168
1262
|
hash_length = rt.hash_length or self.hash_length
|
1169
|
-
resource_template_parameters = self._collect_parameters(rt)
|
1170
|
-
resource_template_secret_parameters = self._collect_secret_parameters(rt)
|
1171
|
-
|
1172
|
-
consolidated_parameters = {}
|
1173
|
-
consolidated_parameters.update(saas_file_parameters)
|
1174
|
-
consolidated_parameters.update(saas_file_secret_parameters)
|
1175
|
-
consolidated_parameters.update(resource_template_parameters)
|
1176
|
-
consolidated_parameters.update(resource_template_secret_parameters)
|
1177
|
-
|
1178
1263
|
# Iterate over targets (each target is a namespace).
|
1179
1264
|
for target in rt.targets:
|
1180
1265
|
if target.disable:
|
@@ -1198,25 +1283,15 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1198
1283
|
|
1199
1284
|
specs.append(
|
1200
1285
|
TargetSpec(
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
managed_resource_types=saas_file.managed_resource_types,
|
1205
|
-
delete=bool(target.delete),
|
1206
|
-
privileged=bool(saas_file.cluster_admin),
|
1286
|
+
saas_file=saas_file,
|
1287
|
+
resource_template=rt,
|
1288
|
+
target=target,
|
1207
1289
|
# process_template options
|
1208
|
-
resource_template_name=rt.name,
|
1209
1290
|
image_auth=image_auth,
|
1210
|
-
url=rt.url,
|
1211
|
-
path=rt.path,
|
1212
|
-
provider=provider,
|
1213
1291
|
hash_length=hash_length,
|
1214
|
-
target=target,
|
1215
|
-
parameters=consolidated_parameters,
|
1216
1292
|
github=github,
|
1217
1293
|
target_config_hash=digest,
|
1218
|
-
|
1219
|
-
image_patterns=saas_file.image_patterns,
|
1294
|
+
secret_reader=self.secret_reader,
|
1220
1295
|
)
|
1221
1296
|
)
|
1222
1297
|
|
@@ -1224,27 +1299,18 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1224
1299
|
|
1225
1300
|
def populate_desired_state_saas_file(
|
1226
1301
|
self, spec: TargetSpec, ri: ResourceInventory
|
1227
|
-
) ->
|
1302
|
+
) -> Promotion | None:
|
1228
1303
|
if spec.delete:
|
1229
1304
|
# to delete resources, we avoid adding them to the desired state
|
1230
1305
|
return None
|
1231
1306
|
|
1307
|
+
html_url = spec.html_url
|
1232
1308
|
try:
|
1233
|
-
resources,
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
path=spec.path,
|
1239
|
-
provider=spec.provider,
|
1240
|
-
hash_length=spec.hash_length,
|
1241
|
-
target=spec.target,
|
1242
|
-
parameters=spec.parameters,
|
1243
|
-
github=spec.github,
|
1244
|
-
target_config_hash=spec.target_config_hash,
|
1245
|
-
)
|
1246
|
-
except Exception:
|
1247
|
-
# log message send in _process_template
|
1309
|
+
resources, promotion = self._process_template(spec)
|
1310
|
+
except Exception as e:
|
1311
|
+
# error log message send in _process_template. We cannot just
|
1312
|
+
# register an error without logging as inventory errors don't have details.
|
1313
|
+
logging.error(f"Error in populate_desired_state_saas_file: {e}")
|
1248
1314
|
ri.register_error()
|
1249
1315
|
return None
|
1250
1316
|
|
@@ -1274,11 +1340,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1274
1340
|
self._additional_resource_process(resources, html_url)
|
1275
1341
|
# check images
|
1276
1342
|
image_error = self._check_images(
|
1277
|
-
|
1278
|
-
resource_template_name=spec.resource_template_name,
|
1279
|
-
image_auth=spec.image_auth,
|
1280
|
-
image_patterns=spec.image_patterns,
|
1281
|
-
html_url=html_url,
|
1343
|
+
spec=spec,
|
1282
1344
|
resources=resources,
|
1283
1345
|
)
|
1284
1346
|
if image_error:
|
@@ -1310,18 +1372,25 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1310
1372
|
+ f"{spec.resource_template_name}."
|
1311
1373
|
)
|
1312
1374
|
logging.error(msg)
|
1375
|
+
except ResourceNotManagedError:
|
1376
|
+
msg = (
|
1377
|
+
f"[{spec.cluster}/{spec.namespace}] desired item "
|
1378
|
+
+ f"not managed, skipping: {oc_resource.kind}/{oc_resource.name}. "
|
1379
|
+
+ f"saas file name: {spec.saas_file_name}, "
|
1380
|
+
+ "resource template name: "
|
1381
|
+
+ f"{spec.resource_template_name}."
|
1382
|
+
)
|
1383
|
+
logging.info(msg)
|
1313
1384
|
|
1314
1385
|
return promotion
|
1315
1386
|
|
1316
1387
|
def get_diff(
|
1317
1388
|
self, trigger_type: TriggerTypes, dry_run: bool
|
1318
1389
|
) -> tuple[
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
list[TriggerSpecContainerImage],
|
1324
|
-
],
|
1390
|
+
list[TriggerSpecConfig]
|
1391
|
+
| list[TriggerSpecMovingCommit]
|
1392
|
+
| list[TriggerSpecUpstreamJob]
|
1393
|
+
| list[TriggerSpecContainerImage],
|
1325
1394
|
bool,
|
1326
1395
|
]:
|
1327
1396
|
if trigger_type == TriggerTypes.MOVING_COMMITS:
|
@@ -1562,10 +1631,10 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1562
1631
|
image_uri = f"{image_registry}:{desired_image_tag}"
|
1563
1632
|
image_auth = self._initiate_image_auth(saas_file)
|
1564
1633
|
error_prefix = f"[{saas_file.name}/{rt.name}] {target.ref}:"
|
1565
|
-
|
1634
|
+
image = self._get_image(
|
1566
1635
|
image_uri, saas_file.image_patterns, image_auth, error_prefix
|
1567
1636
|
)
|
1568
|
-
if
|
1637
|
+
if not image:
|
1569
1638
|
continue
|
1570
1639
|
|
1571
1640
|
trigger_spec = TriggerSpecContainerImage(
|
@@ -1580,7 +1649,9 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1580
1649
|
state_content=desired_image_tag,
|
1581
1650
|
)
|
1582
1651
|
if self.include_trigger_trace:
|
1583
|
-
trigger_spec.reason =
|
1652
|
+
trigger_spec.reason = (
|
1653
|
+
f"{rt.url}/commit/{commit_sha} build {image_uri}"
|
1654
|
+
)
|
1584
1655
|
if not self.state:
|
1585
1656
|
raise Exception("state is not initialized")
|
1586
1657
|
current_image_tag = self.state.get(trigger_spec.state_key, None)
|
@@ -1613,7 +1684,7 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1613
1684
|
return list(itertools.chain.from_iterable(results))
|
1614
1685
|
|
1615
1686
|
@staticmethod
|
1616
|
-
def remove_none_values(d:
|
1687
|
+
def remove_none_values(d: dict[Any, Any] | None) -> dict[Any, Any]:
|
1617
1688
|
if d is None:
|
1618
1689
|
return {}
|
1619
1690
|
new = {}
|
@@ -1647,6 +1718,13 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1647
1718
|
|
1648
1719
|
if self.include_trigger_trace:
|
1649
1720
|
trigger_spec.reason = f"{self.repo_url}/commit/{RunningState().commit}"
|
1721
|
+
# For now we count every saas config change as an auto-promotion
|
1722
|
+
# if the auto promotion field is enabled in the saas target.
|
1723
|
+
# Ideally, we check if there was an actual ref change in order
|
1724
|
+
# to reduce false-positives.
|
1725
|
+
promotion = trigger_spec.state_content.get("promotion")
|
1726
|
+
if promotion and promotion.get("auto", False):
|
1727
|
+
trigger_spec.reason += " [auto-promotion]"
|
1650
1728
|
trigger_specs.append(trigger_spec)
|
1651
1729
|
return trigger_specs
|
1652
1730
|
|
@@ -1690,9 +1768,9 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1690
1768
|
)
|
1691
1769
|
|
1692
1770
|
# add managed resource types to target config
|
1693
|
-
desired_target_config[
|
1694
|
-
|
1695
|
-
|
1771
|
+
desired_target_config["saas_file_managed_resource_types"] = (
|
1772
|
+
saas_file.managed_resource_types
|
1773
|
+
)
|
1696
1774
|
desired_target_config["url"] = rt.url
|
1697
1775
|
desired_target_config["path"] = rt.path
|
1698
1776
|
# before the GQL classes are introduced, the parameters attribute
|
@@ -1702,6 +1780,17 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1702
1780
|
if rt.parameters is not None
|
1703
1781
|
else None
|
1704
1782
|
)
|
1783
|
+
|
1784
|
+
# include secret parameters from resource template and saas file
|
1785
|
+
if rt.secret_parameters:
|
1786
|
+
desired_target_config["rt_secretparameters"] = [
|
1787
|
+
p.dict() for p in rt.secret_parameters
|
1788
|
+
]
|
1789
|
+
if saas_file.secret_parameters:
|
1790
|
+
desired_target_config["saas_file_secretparameters"] = [
|
1791
|
+
p.dict() for p in saas_file.secret_parameters
|
1792
|
+
]
|
1793
|
+
|
1705
1794
|
# Convert to dict, ChainMap is not JSON serializable
|
1706
1795
|
# desired_target_config needs to be serialized to generate
|
1707
1796
|
# its config hash and to be stored in S3
|
@@ -1733,84 +1822,115 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1733
1822
|
"cluster": {"name": True, "server_url": True},
|
1734
1823
|
"app": {"name": True},
|
1735
1824
|
},
|
1825
|
+
# TODO: add environment.parameters to the include list!?!?
|
1736
1826
|
)
|
1737
1827
|
|
1738
|
-
def
|
1739
|
-
|
1740
|
-
If there were promotion sections in the participating saas files
|
1741
|
-
validate that the conditions are met."""
|
1828
|
+
def _validate_promotion(self, promotion: Promotion) -> bool:
|
1829
|
+
# Placing this check here to make mypy happy
|
1742
1830
|
if not (self.state and self._promotion_state):
|
1743
1831
|
raise Exception("state is not initialized")
|
1744
1832
|
|
1745
|
-
|
1746
|
-
|
1747
|
-
continue
|
1748
|
-
# validate that the commit sha being promoted
|
1749
|
-
# was successfully published to the subscribed channel(s)
|
1750
|
-
if promotion.subscribe:
|
1751
|
-
for channel in promotion.subscribe:
|
1752
|
-
info = self._promotion_state.get_promotion_data(
|
1753
|
-
sha=promotion.commit_sha, channel=channel, local_lookup=False
|
1754
|
-
)
|
1755
|
-
if not (info and info.success):
|
1756
|
-
logging.error(
|
1757
|
-
f"Commit {promotion.commit_sha} was not "
|
1758
|
-
+ f"published with success to channel {channel}"
|
1759
|
-
)
|
1760
|
-
return False
|
1761
|
-
state_config_hash = info.target_config_hash
|
1762
|
-
|
1763
|
-
# This code supports current saas targets that does
|
1764
|
-
# not have promotion_data yet
|
1765
|
-
if not state_config_hash or not promotion.promotion_data:
|
1766
|
-
logging.info(
|
1767
|
-
"Promotion data is missing; rely on the success "
|
1768
|
-
"state only"
|
1769
|
-
)
|
1770
|
-
return True
|
1771
|
-
|
1772
|
-
# Validate the promotion_data section.
|
1773
|
-
# Just validate parent_saas_config hash
|
1774
|
-
# promotion_data type by now.
|
1775
|
-
parent_saas_config = None
|
1776
|
-
for pd in promotion.promotion_data:
|
1777
|
-
if pd.channel == channel:
|
1778
|
-
for data in pd.data or []:
|
1779
|
-
if isinstance(data, SaasParentSaasPromotion):
|
1780
|
-
parent_saas_config = data
|
1781
|
-
|
1782
|
-
# This section might not exist due to a manual MR.
|
1783
|
-
# Promotion shall continue if this data is missing.
|
1784
|
-
# The parent at the same ref has succeed if this code
|
1785
|
-
# is reached though.
|
1786
|
-
if not parent_saas_config:
|
1787
|
-
logging.info(
|
1788
|
-
"Parent Saas config missing on target "
|
1789
|
-
"rely on the success state only"
|
1790
|
-
)
|
1791
|
-
return True
|
1833
|
+
if not promotion.subscribe:
|
1834
|
+
return True
|
1792
1835
|
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
return True
|
1836
|
+
if promotion.commit_sha in self.blocked_versions.get(promotion.url, set()):
|
1837
|
+
logging.error(f"Commit {promotion.commit_sha} is blocked!")
|
1838
|
+
return False
|
1797
1839
|
|
1840
|
+
# hotfix must run before further gates are evaluated to override them
|
1841
|
+
if promotion.commit_sha in self.hotfix_versions.get(promotion.url, set()):
|
1842
|
+
return True
|
1843
|
+
|
1844
|
+
now = datetime.now(UTC)
|
1845
|
+
passed_soak_days = timedelta(days=0)
|
1846
|
+
|
1847
|
+
for channel in promotion.subscribe:
|
1848
|
+
config_hashes: set[str] = set()
|
1849
|
+
for target_uid in channel.publisher_uids:
|
1850
|
+
deployment = self._promotion_state.get_promotion_data(
|
1851
|
+
sha=promotion.commit_sha,
|
1852
|
+
channel=channel.name,
|
1853
|
+
target_uid=target_uid,
|
1854
|
+
pre_check_sha_exists=False,
|
1855
|
+
)
|
1856
|
+
if not (
|
1857
|
+
deployment and (deployment.success or deployment.has_succeeded_once)
|
1858
|
+
):
|
1798
1859
|
logging.error(
|
1799
|
-
"
|
1800
|
-
"
|
1801
|
-
"Check if other MR exists for this target, "
|
1802
|
-
f"or update {parent_saas_config.target_config_hash} "
|
1803
|
-
f"to {state_config_hash} for channel {channel}"
|
1860
|
+
f"Commit {promotion.commit_sha} was not "
|
1861
|
+
+ f"published with success to channel {channel.name}"
|
1804
1862
|
)
|
1805
1863
|
return False
|
1864
|
+
if check_in := deployment.check_in:
|
1865
|
+
passed_soak_days += now - datetime.fromisoformat(check_in)
|
1866
|
+
if deployment.target_config_hash:
|
1867
|
+
config_hashes.add(deployment.target_config_hash)
|
1868
|
+
|
1869
|
+
# This code supports current saas targets that does
|
1870
|
+
# not have promotion_data yet
|
1871
|
+
if not config_hashes or not promotion.promotion_data:
|
1872
|
+
logging.info(
|
1873
|
+
"Promotion data is missing; rely on the success " "state only"
|
1874
|
+
)
|
1875
|
+
continue
|
1876
|
+
|
1877
|
+
# Validate the promotion_data section.
|
1878
|
+
# Just validate parent_saas_config hash
|
1879
|
+
# promotion_data type by now.
|
1880
|
+
parent_saas_config = None
|
1881
|
+
for pd in promotion.promotion_data:
|
1882
|
+
if pd.channel == channel.name:
|
1883
|
+
for data in pd.data or []:
|
1884
|
+
if isinstance(data, SaasParentSaasPromotion):
|
1885
|
+
parent_saas_config = data
|
1886
|
+
|
1887
|
+
# This section might not exist due to a manual MR.
|
1888
|
+
# Promotion shall continue if this data is missing.
|
1889
|
+
# The parent at the same ref has succeed if this code
|
1890
|
+
# is reached though.
|
1891
|
+
if not parent_saas_config:
|
1892
|
+
logging.info(
|
1893
|
+
"Parent Saas config missing on target "
|
1894
|
+
"rely on the success state only"
|
1895
|
+
)
|
1896
|
+
continue
|
1897
|
+
|
1898
|
+
# Validate that the state config_hash set by the parent
|
1899
|
+
# matches with the hash set in promotion_data
|
1900
|
+
if parent_saas_config.target_config_hash in config_hashes:
|
1901
|
+
continue
|
1902
|
+
|
1903
|
+
logging.error(
|
1904
|
+
"Parent saas target has run with a newer "
|
1905
|
+
"configuration and the same commit (ref). "
|
1906
|
+
"Check if other MR exists for this target, "
|
1907
|
+
f"or update {parent_saas_config.target_config_hash} "
|
1908
|
+
f"to any in {config_hashes} for channel {channel.name}"
|
1909
|
+
)
|
1910
|
+
return False
|
1911
|
+
|
1912
|
+
if passed_soak_days < timedelta(days=promotion.soak_days):
|
1913
|
+
logging.error(
|
1914
|
+
f"SoakDays in publishers did not pass. So far accumulated soakDays is {passed_soak_days},"
|
1915
|
+
f"but we have a soakDays setting of {promotion.soak_days}. We cannot proceed with this promotion."
|
1916
|
+
)
|
1917
|
+
return False
|
1806
1918
|
return True
|
1807
1919
|
|
1920
|
+
def validate_promotions(self) -> bool:
|
1921
|
+
"""
|
1922
|
+
If there were promotion sections in the participating saas files
|
1923
|
+
validate that the conditions are met."""
|
1924
|
+
return all(
|
1925
|
+
self._validate_promotion(promotion)
|
1926
|
+
for promotion in self.promotions
|
1927
|
+
if promotion is not None
|
1928
|
+
)
|
1929
|
+
|
1808
1930
|
def publish_promotions(
|
1809
1931
|
self,
|
1810
1932
|
success: bool,
|
1811
1933
|
all_saas_files: Iterable[SaasFile],
|
1812
|
-
mr_cli: MRClient,
|
1813
|
-
auto_promote: bool = False,
|
1814
1934
|
) -> None:
|
1815
1935
|
"""
|
1816
1936
|
If there were promotion sections in the participating saas file
|
@@ -1819,17 +1939,11 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1819
1939
|
subscribe_saas_file_path_map,
|
1820
1940
|
subscribe_target_path_map,
|
1821
1941
|
) = self._get_subscribe_path_map(all_saas_files, auto_only=True)
|
1822
|
-
trigger_promotion = False
|
1823
|
-
|
1824
|
-
if self.promotions and not auto_promote:
|
1825
|
-
logging.info(
|
1826
|
-
"Auto-promotions to next stages are disabled. "
|
1827
|
-
"Promotions are being handled by SAPM."
|
1828
|
-
)
|
1829
1942
|
|
1830
1943
|
if not (self.state and self._promotion_state):
|
1831
1944
|
raise Exception("state is not initialized")
|
1832
1945
|
|
1946
|
+
now = datetime.now(UTC)
|
1833
1947
|
for promotion in self.promotions:
|
1834
1948
|
if promotion is None:
|
1835
1949
|
continue
|
@@ -1838,14 +1952,43 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1838
1952
|
all_subscribed_saas_file_paths = set()
|
1839
1953
|
all_subscribed_target_paths = set()
|
1840
1954
|
for channel in promotion.publish:
|
1955
|
+
# make sure we keep some attributes on re-deployments of same ref
|
1956
|
+
has_succeeded_once = success
|
1957
|
+
current_state = self._promotion_state.get_promotion_data(
|
1958
|
+
sha=promotion.commit_sha,
|
1959
|
+
channel=channel,
|
1960
|
+
target_uid=promotion.saas_target_uid,
|
1961
|
+
use_cache=True,
|
1962
|
+
)
|
1963
|
+
if current_state and current_state.has_succeeded_once:
|
1964
|
+
has_succeeded_once = True
|
1965
|
+
|
1966
|
+
check_in = str(now)
|
1967
|
+
if (
|
1968
|
+
success
|
1969
|
+
and current_state
|
1970
|
+
and current_state.check_in
|
1971
|
+
and current_state.success
|
1972
|
+
):
|
1973
|
+
# We want to avoid an override of the timestamp.
|
1974
|
+
# This can happen on re-deployments of the same ref.
|
1975
|
+
# We only re-use the check_in time if the previous
|
1976
|
+
# and current deployment was successful.
|
1977
|
+
# On unsuccessful deployments, we
|
1978
|
+
# update the check_in time to current time.
|
1979
|
+
check_in = current_state.check_in
|
1980
|
+
|
1841
1981
|
# publish to state to pass promotion gate
|
1842
1982
|
self._promotion_state.publish_promotion_data(
|
1843
1983
|
sha=promotion.commit_sha,
|
1844
1984
|
channel=channel,
|
1985
|
+
target_uid=promotion.saas_target_uid,
|
1845
1986
|
data=PromotionData(
|
1846
1987
|
saas_file=promotion.saas_file,
|
1847
1988
|
success=success,
|
1848
1989
|
target_config_hash=promotion.target_config_hash,
|
1990
|
+
has_succeeded_once=has_succeeded_once,
|
1991
|
+
check_in=check_in,
|
1849
1992
|
),
|
1850
1993
|
)
|
1851
1994
|
logging.info(
|
@@ -1869,19 +2012,6 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1869
2012
|
promotion.saas_file_paths = list(all_subscribed_saas_file_paths)
|
1870
2013
|
promotion.target_paths = list(all_subscribed_target_paths)
|
1871
2014
|
|
1872
|
-
if auto_promote and (
|
1873
|
-
all_subscribed_saas_file_paths or all_subscribed_target_paths
|
1874
|
-
):
|
1875
|
-
trigger_promotion = True
|
1876
|
-
|
1877
|
-
if success and trigger_promotion:
|
1878
|
-
from reconcile.utils.mr.auto_promoter import (
|
1879
|
-
AutoPromoter, # avoid circular import
|
1880
|
-
)
|
1881
|
-
|
1882
|
-
mr = AutoPromoter([p for p in self.promotions if p is not None])
|
1883
|
-
mr.submit(cli=mr_cli)
|
1884
|
-
|
1885
2015
|
@staticmethod
|
1886
2016
|
def _get_subscribe_path_map(
|
1887
2017
|
saas_files: Iterable[SaasFile], auto_only: bool = False
|
@@ -1917,8 +2047,8 @@ class SaasHerder: # pylint: disable=too-many-public-methods
|
|
1917
2047
|
@staticmethod
|
1918
2048
|
def resolve_templated_parameters(saas_files: Iterable[SaasFile]) -> None:
|
1919
2049
|
"""Resolve templated target parameters in saas files."""
|
1920
|
-
from reconcile.
|
1921
|
-
compile_jinja2_template,
|
2050
|
+
from reconcile.utils.jinja2.utils import ( # noqa: PLC0415 - # avoid circular import
|
2051
|
+
compile_jinja2_template,
|
1922
2052
|
)
|
1923
2053
|
|
1924
2054
|
for saas_file in saas_files:
|