qontract-reconcile 0.10.2.dev334__py3-none-any.whl → 0.10.2.dev439__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/METADATA +13 -12
- {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/RECORD +366 -361
- reconcile/acs_rbac.py +2 -2
- reconcile/aus/advanced_upgrade_service.py +18 -12
- reconcile/aus/base.py +134 -32
- reconcile/aus/cluster_version_data.py +15 -5
- reconcile/aus/models.py +3 -1
- reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
- reconcile/aus/ocm_upgrade_scheduler.py +8 -1
- reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
- reconcile/aus/version_gates/sts_version_gate_handler.py +54 -1
- reconcile/automated_actions/config/integration.py +16 -4
- reconcile/aws_account_manager/integration.py +8 -8
- reconcile/aws_account_manager/reconciler.py +3 -3
- reconcile/aws_ami_cleanup/integration.py +8 -12
- reconcile/aws_ami_share.py +69 -62
- reconcile/aws_cloudwatch_log_retention/integration.py +155 -126
- reconcile/aws_ecr_image_pull_secrets.py +3 -3
- reconcile/aws_iam_keys.py +1 -0
- reconcile/aws_saml_idp/integration.py +12 -4
- reconcile/aws_saml_roles/integration.py +32 -25
- reconcile/aws_version_sync/integration.py +6 -12
- reconcile/change_owners/bundle.py +3 -3
- reconcile/change_owners/change_log_tracking.py +3 -2
- reconcile/change_owners/change_owners.py +1 -1
- reconcile/change_owners/diff.py +2 -4
- reconcile/checkpoint.py +11 -3
- reconcile/cli.py +111 -18
- reconcile/dashdotdb_dora.py +5 -12
- reconcile/dashdotdb_slo.py +1 -1
- reconcile/database_access_manager.py +123 -117
- reconcile/dynatrace_token_provider/integration.py +1 -1
- reconcile/endpoints_discovery/integration.py +4 -1
- reconcile/endpoints_discovery/merge_request.py +1 -1
- reconcile/endpoints_discovery/merge_request_manager.py +9 -11
- reconcile/external_resources/factories.py +5 -12
- reconcile/external_resources/integration.py +1 -1
- reconcile/external_resources/manager.py +8 -5
- reconcile/external_resources/meta.py +0 -1
- reconcile/external_resources/metrics.py +1 -1
- reconcile/external_resources/model.py +20 -20
- reconcile/external_resources/reconciler.py +7 -4
- reconcile/external_resources/secrets_sync.py +8 -11
- reconcile/external_resources/state.py +26 -16
- reconcile/fleet_labeler/integration.py +1 -1
- reconcile/gabi_authorized_users.py +8 -5
- reconcile/gcp_image_mirror.py +2 -2
- reconcile/github_org.py +1 -1
- reconcile/github_owners.py +4 -0
- reconcile/gitlab_housekeeping.py +13 -15
- reconcile/gitlab_members.py +6 -12
- reconcile/gitlab_mr_sqs_consumer.py +2 -2
- reconcile/gitlab_owners.py +15 -11
- reconcile/gitlab_permissions.py +8 -12
- reconcile/glitchtip_project_alerts/integration.py +3 -1
- reconcile/gql_definitions/acs/acs_instances.py +10 -10
- reconcile/gql_definitions/acs/acs_policies.py +5 -5
- reconcile/gql_definitions/acs/acs_rbac.py +6 -6
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +32 -32
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +26 -26
- reconcile/gql_definitions/app_interface_metrics_exporter/onboarding_status.py +6 -7
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/roles.py +5 -5
- reconcile/gql_definitions/app_sre_tekton_access_revalidation/users.py +5 -5
- reconcile/gql_definitions/automated_actions/instance.py +51 -12
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +11 -11
- reconcile/gql_definitions/aws_ami_cleanup/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +28 -68
- reconcile/gql_definitions/aws_saml_idp/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_saml_roles/aws_accounts.py +20 -10
- reconcile/gql_definitions/aws_saml_roles/roles.py +5 -5
- reconcile/gql_definitions/aws_version_sync/clusters.py +10 -10
- reconcile/gql_definitions/aws_version_sync/namespaces.py +5 -5
- reconcile/gql_definitions/change_owners/queries/change_types.py +5 -5
- reconcile/gql_definitions/change_owners/queries/self_service_roles.py +9 -9
- reconcile/gql_definitions/cluster_auth_rhidp/clusters.py +18 -18
- reconcile/gql_definitions/common/alerting_services_settings.py +9 -9
- reconcile/gql_definitions/common/app_code_component_repos.py +5 -5
- reconcile/gql_definitions/common/app_interface_custom_messages.py +5 -5
- reconcile/gql_definitions/common/app_interface_dms_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_repo_settings.py +5 -5
- reconcile/gql_definitions/common/app_interface_roles.py +120 -0
- reconcile/gql_definitions/common/app_interface_state_settings.py +10 -10
- reconcile/gql_definitions/common/app_interface_vault_settings.py +5 -5
- reconcile/gql_definitions/common/app_quay_repos_escalation_policies.py +5 -5
- reconcile/gql_definitions/common/apps.py +5 -5
- reconcile/gql_definitions/common/aws_vpc_requests.py +22 -9
- reconcile/gql_definitions/common/aws_vpcs.py +11 -11
- reconcile/gql_definitions/common/clusters.py +37 -35
- reconcile/gql_definitions/common/clusters_minimal.py +14 -14
- reconcile/gql_definitions/common/clusters_with_dms.py +6 -6
- reconcile/gql_definitions/common/clusters_with_peering.py +29 -30
- reconcile/gql_definitions/common/github_orgs.py +10 -10
- reconcile/gql_definitions/common/jira_settings.py +10 -10
- reconcile/gql_definitions/common/jiralert_settings.py +5 -5
- reconcile/gql_definitions/common/ldap_settings.py +5 -5
- reconcile/gql_definitions/common/namespaces.py +42 -44
- reconcile/gql_definitions/common/namespaces_minimal.py +15 -13
- reconcile/gql_definitions/common/ocm_env_telemeter.py +12 -12
- reconcile/gql_definitions/common/ocm_environments.py +19 -19
- reconcile/gql_definitions/common/pagerduty_instances.py +9 -9
- reconcile/gql_definitions/common/pgp_reencryption_settings.py +6 -6
- reconcile/gql_definitions/common/pipeline_providers.py +29 -29
- reconcile/gql_definitions/common/quay_instances.py +5 -5
- reconcile/gql_definitions/common/quay_orgs.py +5 -5
- reconcile/gql_definitions/common/reserved_networks.py +5 -5
- reconcile/gql_definitions/common/rhcs_provider_settings.py +5 -5
- reconcile/gql_definitions/common/saas_files.py +44 -44
- reconcile/gql_definitions/common/saas_target_namespaces.py +10 -10
- reconcile/gql_definitions/common/saasherder_settings.py +5 -5
- reconcile/gql_definitions/common/slack_workspaces.py +5 -5
- reconcile/gql_definitions/common/smtp_client_settings.py +19 -19
- reconcile/gql_definitions/common/state_aws_account.py +7 -8
- reconcile/gql_definitions/common/users.py +5 -5
- reconcile/gql_definitions/common/users_with_paths.py +5 -5
- reconcile/gql_definitions/cost_report/app_names.py +5 -5
- reconcile/gql_definitions/cost_report/cost_namespaces.py +5 -5
- reconcile/gql_definitions/cost_report/settings.py +9 -9
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +43 -43
- reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py +10 -10
- reconcile/gql_definitions/dynatrace_token_provider/token_specs.py +5 -5
- reconcile/gql_definitions/email_sender/apps.py +5 -5
- reconcile/gql_definitions/email_sender/emails.py +8 -8
- reconcile/gql_definitions/email_sender/users.py +6 -6
- reconcile/gql_definitions/endpoints_discovery/apps.py +10 -10
- reconcile/gql_definitions/external_resources/aws_accounts.py +9 -9
- reconcile/gql_definitions/external_resources/external_resources_modules.py +23 -23
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +494 -410
- reconcile/gql_definitions/external_resources/external_resources_settings.py +28 -26
- reconcile/gql_definitions/external_resources/fragments/external_resources_module_overrides.py +5 -5
- reconcile/gql_definitions/fleet_labeler/fleet_labels.py +40 -40
- reconcile/gql_definitions/fragments/aus_organization.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_common.py +7 -5
- reconcile/gql_definitions/fragments/aws_account_managed.py +5 -5
- reconcile/gql_definitions/fragments/aws_account_sso.py +5 -5
- reconcile/gql_definitions/fragments/aws_infra_management_account.py +5 -5
- reconcile/gql_definitions/fragments/{aws_vpc_request_subnet.py → aws_organization.py} +12 -8
- reconcile/gql_definitions/fragments/aws_vpc.py +5 -5
- reconcile/gql_definitions/fragments/aws_vpc_request.py +12 -5
- reconcile/gql_definitions/fragments/container_image_mirror.py +5 -5
- reconcile/gql_definitions/fragments/deploy_resources.py +5 -5
- reconcile/gql_definitions/fragments/disable.py +5 -5
- reconcile/gql_definitions/fragments/email_service.py +5 -5
- reconcile/gql_definitions/fragments/email_user.py +5 -5
- reconcile/gql_definitions/fragments/jumphost_common_fields.py +5 -5
- reconcile/gql_definitions/fragments/membership_source.py +5 -5
- reconcile/gql_definitions/fragments/minimal_ocm_organization.py +5 -5
- reconcile/gql_definitions/fragments/oc_connection_cluster.py +5 -5
- reconcile/gql_definitions/fragments/ocm_environment.py +5 -5
- reconcile/gql_definitions/fragments/pipeline_provider_retention.py +5 -5
- reconcile/gql_definitions/fragments/prometheus_instance.py +5 -5
- reconcile/gql_definitions/fragments/resource_limits_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_requests_requirements.py +5 -5
- reconcile/gql_definitions/fragments/resource_values.py +5 -5
- reconcile/gql_definitions/fragments/saas_slo_document.py +5 -5
- reconcile/gql_definitions/fragments/saas_target_namespace.py +5 -5
- reconcile/gql_definitions/fragments/serviceaccount_token.py +5 -5
- reconcile/gql_definitions/fragments/terraform_state.py +5 -5
- reconcile/gql_definitions/fragments/upgrade_policy.py +5 -5
- reconcile/gql_definitions/fragments/user.py +5 -5
- reconcile/gql_definitions/fragments/vault_secret.py +5 -5
- reconcile/gql_definitions/gcp/gcp_docker_repos.py +9 -9
- reconcile/gql_definitions/gcp/gcp_projects.py +9 -9
- reconcile/gql_definitions/gitlab_members/gitlab_instances.py +9 -9
- reconcile/gql_definitions/gitlab_members/permissions.py +9 -9
- reconcile/gql_definitions/glitchtip/glitchtip_instance.py +9 -9
- reconcile/gql_definitions/glitchtip/glitchtip_project.py +11 -11
- reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py +9 -9
- reconcile/gql_definitions/integrations/integrations.py +48 -51
- reconcile/gql_definitions/introspection.json +3207 -1683
- reconcile/gql_definitions/jenkins_configs/jenkins_configs.py +11 -11
- reconcile/gql_definitions/jenkins_configs/jenkins_instances.py +10 -10
- reconcile/gql_definitions/jira/jira_servers.py +5 -5
- reconcile/gql_definitions/jira_permissions_validator/jira_boards_for_permissions_validator.py +14 -10
- reconcile/gql_definitions/jumphosts/jumphosts.py +13 -13
- reconcile/gql_definitions/ldap_groups/roles.py +5 -5
- reconcile/gql_definitions/ldap_groups/settings.py +9 -9
- reconcile/gql_definitions/maintenance/maintenances.py +5 -5
- reconcile/gql_definitions/membershipsources/roles.py +5 -5
- reconcile/gql_definitions/ocm_labels/clusters.py +18 -19
- reconcile/gql_definitions/ocm_labels/organizations.py +5 -5
- reconcile/gql_definitions/openshift_cluster_bots/clusters.py +22 -22
- reconcile/gql_definitions/openshift_groups/managed_groups.py +5 -5
- reconcile/gql_definitions/openshift_groups/managed_roles.py +6 -6
- reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py +10 -10
- reconcile/gql_definitions/quay_membership/quay_membership.py +6 -6
- reconcile/gql_definitions/rhcs/certs.py +33 -87
- reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
- reconcile/gql_definitions/rhidp/organizations.py +18 -18
- reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py +5 -5
- reconcile/gql_definitions/service_dependencies/service_dependencies.py +8 -8
- reconcile/gql_definitions/sharding/aws_accounts.py +10 -10
- reconcile/gql_definitions/sharding/ocm_organization.py +8 -8
- reconcile/gql_definitions/skupper_network/site_controller_template.py +5 -5
- reconcile/gql_definitions/skupper_network/skupper_networks.py +10 -10
- reconcile/gql_definitions/slack_usergroups/clusters.py +5 -5
- reconcile/gql_definitions/slack_usergroups/permissions.py +9 -9
- reconcile/gql_definitions/slack_usergroups/users.py +5 -5
- reconcile/gql_definitions/slo_documents/slo_documents.py +5 -5
- reconcile/gql_definitions/status_board/status_board.py +6 -7
- reconcile/gql_definitions/statuspage/statuspages.py +9 -9
- reconcile/gql_definitions/templating/template_collection.py +5 -5
- reconcile/gql_definitions/templating/templates.py +5 -5
- reconcile/gql_definitions/terraform_cloudflare_dns/app_interface_cloudflare_dns_settings.py +6 -6
- reconcile/gql_definitions/terraform_cloudflare_dns/terraform_cloudflare_zones.py +11 -11
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_accounts.py +11 -11
- reconcile/gql_definitions/terraform_cloudflare_resources/terraform_cloudflare_resources.py +20 -25
- reconcile/gql_definitions/terraform_cloudflare_users/app_interface_setting_cloudflare_and_vault.py +6 -6
- reconcile/gql_definitions/terraform_cloudflare_users/terraform_cloudflare_roles.py +12 -12
- reconcile/gql_definitions/terraform_init/aws_accounts.py +23 -9
- reconcile/gql_definitions/terraform_repo/terraform_repo.py +9 -9
- reconcile/gql_definitions/terraform_resources/database_access_manager.py +5 -5
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +440 -407
- reconcile/gql_definitions/terraform_tgw_attachments/aws_accounts.py +23 -17
- reconcile/gql_definitions/unleash_feature_toggles/feature_toggles.py +9 -9
- reconcile/gql_definitions/vault_instances/vault_instances.py +61 -61
- reconcile/gql_definitions/vault_policies/vault_policies.py +11 -11
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +8 -8
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +5 -5
- reconcile/integrations_manager.py +3 -3
- reconcile/jenkins_worker_fleets.py +10 -8
- reconcile/jira_permissions_validator.py +237 -122
- reconcile/ldap_groups/integration.py +1 -1
- reconcile/ocm/types.py +35 -56
- reconcile/ocm_aws_infrastructure_access.py +1 -1
- reconcile/ocm_clusters.py +4 -4
- reconcile/ocm_labels/integration.py +3 -2
- reconcile/ocm_machine_pools.py +33 -27
- reconcile/openshift_base.py +113 -5
- reconcile/openshift_cluster_bots.py +3 -2
- reconcile/openshift_namespace_labels.py +1 -1
- reconcile/openshift_namespaces.py +97 -101
- reconcile/openshift_resources_base.py +6 -2
- reconcile/openshift_rhcs_certs.py +74 -37
- reconcile/openshift_rolebindings.py +230 -130
- reconcile/openshift_saas_deploy.py +6 -7
- reconcile/openshift_saas_deploy_change_tester.py +9 -7
- reconcile/openshift_saas_deploy_trigger_cleaner.py +3 -5
- reconcile/openshift_serviceaccount_tokens.py +2 -2
- reconcile/openshift_upgrade_watcher.py +4 -4
- reconcile/openshift_users.py +5 -3
- reconcile/oum/labelset.py +5 -3
- reconcile/oum/models.py +1 -4
- reconcile/prometheus_rules_tester/integration.py +3 -3
- reconcile/quay_mirror.py +1 -1
- reconcile/queries.py +131 -0
- reconcile/rhidp/common.py +3 -5
- reconcile/rhidp/sso_client/base.py +16 -5
- reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
- reconcile/saas_auto_promotions_manager/subscriber.py +4 -3
- reconcile/skupper_network/integration.py +2 -2
- reconcile/slack_usergroups.py +35 -14
- reconcile/sql_query.py +1 -0
- reconcile/status_board.py +6 -6
- reconcile/statuspage/atlassian.py +7 -7
- reconcile/statuspage/integrations/maintenances.py +4 -3
- reconcile/statuspage/page.py +4 -9
- reconcile/statuspage/status.py +5 -8
- reconcile/templates/rosa-classic-cluster-creation.sh.j2 +5 -1
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +4 -1
- reconcile/templating/lib/merge_request_manager.py +2 -2
- reconcile/templating/lib/rendering.py +3 -3
- reconcile/templating/renderer.py +12 -13
- reconcile/terraform_aws_route53.py +7 -1
- reconcile/terraform_cloudflare_dns.py +3 -3
- reconcile/terraform_cloudflare_resources.py +5 -5
- reconcile/terraform_cloudflare_users.py +3 -2
- reconcile/terraform_init/integration.py +187 -23
- reconcile/terraform_repo.py +16 -12
- reconcile/terraform_resources.py +17 -7
- reconcile/terraform_tgw_attachments.py +27 -19
- reconcile/terraform_users.py +7 -0
- reconcile/terraform_vpc_peerings.py +14 -3
- reconcile/terraform_vpc_resources/integration.py +20 -8
- reconcile/typed_queries/app_interface_roles.py +10 -0
- reconcile/typed_queries/aws_account_tags.py +41 -0
- reconcile/typed_queries/cost_report/app_names.py +1 -1
- reconcile/typed_queries/cost_report/cost_namespaces.py +2 -2
- reconcile/typed_queries/saas_files.py +13 -13
- reconcile/typed_queries/status_board.py +2 -2
- reconcile/unleash_feature_toggles/integration.py +4 -2
- reconcile/utils/acs/base.py +6 -3
- reconcile/utils/acs/policies.py +2 -2
- reconcile/utils/aggregated_list.py +4 -3
- reconcile/utils/aws_api.py +51 -20
- reconcile/utils/aws_api_typed/api.py +38 -9
- reconcile/utils/aws_api_typed/cloudformation.py +149 -0
- reconcile/utils/aws_api_typed/logs.py +73 -0
- reconcile/utils/aws_api_typed/organization.py +4 -2
- reconcile/utils/binary.py +7 -12
- reconcile/utils/datetime_util.py +67 -0
- reconcile/utils/deadmanssnitch_api.py +1 -1
- reconcile/utils/differ.py +2 -3
- reconcile/utils/early_exit_cache.py +11 -12
- reconcile/utils/expiration.py +7 -3
- reconcile/utils/external_resource_spec.py +24 -1
- reconcile/utils/filtering.py +1 -1
- reconcile/utils/gitlab_api.py +7 -5
- reconcile/utils/glitchtip/client.py +6 -2
- reconcile/utils/glitchtip/models.py +25 -28
- reconcile/utils/gql.py +4 -7
- reconcile/utils/helm.py +2 -1
- reconcile/utils/helpers.py +1 -1
- reconcile/utils/instrumented_wrappers.py +1 -1
- reconcile/utils/internal_groups/client.py +2 -2
- reconcile/utils/internal_groups/models.py +8 -17
- reconcile/utils/jinja2/utils.py +6 -8
- reconcile/utils/jira_client.py +82 -63
- reconcile/utils/jjb_client.py +28 -15
- reconcile/utils/jobcontroller/controller.py +2 -2
- reconcile/utils/jobcontroller/models.py +17 -1
- reconcile/utils/json.py +74 -0
- reconcile/utils/membershipsources/app_interface_resolver.py +4 -2
- reconcile/utils/membershipsources/models.py +16 -23
- reconcile/utils/membershipsources/resolver.py +4 -2
- reconcile/utils/merge_request_manager/merge_request_manager.py +4 -4
- reconcile/utils/merge_request_manager/parser.py +6 -6
- reconcile/utils/metrics.py +5 -5
- reconcile/utils/models.py +304 -82
- reconcile/utils/mr/app_interface_reporter.py +2 -2
- reconcile/utils/mr/base.py +2 -2
- reconcile/utils/mr/notificator.py +3 -3
- reconcile/utils/mr/update_access_report_base.py +3 -4
- reconcile/utils/mr/user_maintenance.py +3 -2
- reconcile/utils/oc.py +249 -203
- reconcile/utils/oc_filters.py +3 -3
- reconcile/utils/ocm/addons.py +0 -1
- reconcile/utils/ocm/base.py +18 -21
- reconcile/utils/ocm/cluster_groups.py +1 -1
- reconcile/utils/ocm/identity_providers.py +2 -2
- reconcile/utils/ocm/labels.py +1 -1
- reconcile/utils/ocm/products.py +9 -3
- reconcile/utils/ocm/search_filters.py +3 -6
- reconcile/utils/ocm/service_log.py +4 -6
- reconcile/utils/ocm/sre_capability_labels.py +20 -13
- reconcile/utils/openshift_resource.py +10 -5
- reconcile/utils/output.py +3 -2
- reconcile/utils/pagerduty_api.py +10 -7
- reconcile/utils/promotion_state.py +6 -11
- reconcile/utils/raw_github_api.py +1 -1
- reconcile/utils/rhcsv2_certs.py +138 -35
- reconcile/utils/rosa/session.py +16 -0
- reconcile/utils/runtime/integration.py +2 -3
- reconcile/utils/runtime/runner.py +2 -2
- reconcile/utils/saasherder/interfaces.py +13 -20
- reconcile/utils/saasherder/models.py +25 -21
- reconcile/utils/saasherder/saasherder.py +55 -31
- reconcile/utils/slack_api.py +26 -4
- reconcile/utils/sloth.py +224 -0
- reconcile/utils/sqs_gateway.py +2 -1
- reconcile/utils/state.py +2 -1
- reconcile/utils/structs.py +1 -1
- reconcile/utils/terraform_client.py +5 -4
- reconcile/utils/terrascript_aws_client.py +192 -114
- reconcile/utils/unleash/server.py +2 -8
- reconcile/utils/vault.py +5 -12
- reconcile/utils/vcs.py +8 -8
- reconcile/vault_replication.py +107 -42
- tools/app_interface_reporter.py +4 -4
- tools/cli_commands/cost_report/cost_management_api.py +3 -3
- tools/cli_commands/cost_report/view.py +7 -6
- tools/cli_commands/erv2.py +1 -1
- tools/cli_commands/systems_and_tools.py +5 -1
- tools/qontract_cli.py +31 -18
- tools/template_validation.py +3 -1
- reconcile/gql_definitions/cna/__init__.py +0 -0
- reconcile/gql_definitions/cna/queries/__init__.py +0 -0
- reconcile/gql_definitions/ocm_oidc_idp/__init__.py +0 -0
- reconcile/gql_definitions/ocm_subscription_labels/__init__.py +0 -0
- {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev334.dist-info → qontract_reconcile-0.10.2.dev439.dist-info}/entry_points.txt +0 -0
reconcile/statuspage/page.py
CHANGED
|
@@ -19,19 +19,17 @@ from reconcile.statuspage.status import (
|
|
|
19
19
|
PROVIDER_NAME = "statuspage"
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
class StatusComponent(BaseModel):
|
|
22
|
+
class StatusComponent(BaseModel, arbitrary_types_allowed=True):
|
|
23
23
|
"""
|
|
24
24
|
Represents a status page component from the desired state.
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
name: str
|
|
28
28
|
display_name: str
|
|
29
|
-
description: str | None
|
|
30
|
-
group_name: str | None
|
|
29
|
+
description: str | None = None
|
|
30
|
+
group_name: str | None = None
|
|
31
|
+
# Status provider configs hold different ways for a component to determine its status
|
|
31
32
|
status_provider_configs: list[StatusProvider]
|
|
32
|
-
"""
|
|
33
|
-
Status provider configs hold different ways for a component to determine its status
|
|
34
|
-
"""
|
|
35
33
|
|
|
36
34
|
def status_management_enabled(self) -> bool:
|
|
37
35
|
"""
|
|
@@ -49,9 +47,6 @@ class StatusComponent(BaseModel):
|
|
|
49
47
|
return "operational"
|
|
50
48
|
return None
|
|
51
49
|
|
|
52
|
-
class Config:
|
|
53
|
-
arbitrary_types_allowed = True
|
|
54
|
-
|
|
55
50
|
def __eq__(self, other: object) -> bool:
|
|
56
51
|
if not isinstance(other, StatusComponent):
|
|
57
52
|
raise NotImplementedError("Cannot compare to non StatusComponent objects.")
|
reconcile/statuspage/status.py
CHANGED
|
@@ -2,18 +2,15 @@ from abc import (
|
|
|
2
2
|
ABC,
|
|
3
3
|
abstractmethod,
|
|
4
4
|
)
|
|
5
|
-
from datetime import
|
|
6
|
-
UTC,
|
|
7
|
-
datetime,
|
|
8
|
-
)
|
|
5
|
+
from datetime import datetime
|
|
9
6
|
|
|
10
|
-
from dateutil.parser import isoparse
|
|
11
7
|
from pydantic import BaseModel
|
|
12
8
|
|
|
13
9
|
from reconcile.gql_definitions.statuspage.statuspages import (
|
|
14
10
|
ManualStatusProviderV1,
|
|
15
11
|
StatusProviderV1,
|
|
16
12
|
)
|
|
13
|
+
from reconcile.utils.datetime_util import from_utc_iso_format, utc_now
|
|
17
14
|
|
|
18
15
|
# This module defines the interface for status providers for components on status
|
|
19
16
|
# pages. A status provider is responsible for determining the status of a component.
|
|
@@ -70,7 +67,7 @@ class ManualStatusProvider(StatusProvider, BaseModel):
|
|
|
70
67
|
raise ValueError(
|
|
71
68
|
"manual component status time window is invalid: end before start"
|
|
72
69
|
)
|
|
73
|
-
now =
|
|
70
|
+
now = utc_now()
|
|
74
71
|
if self.start and now < self.start:
|
|
75
72
|
return False
|
|
76
73
|
return not (self.end and self.end < now)
|
|
@@ -84,8 +81,8 @@ def build_status_provider_config(
|
|
|
84
81
|
provider specific implementation that provides the status resolution logic.
|
|
85
82
|
"""
|
|
86
83
|
if isinstance(cfg, ManualStatusProviderV1):
|
|
87
|
-
start =
|
|
88
|
-
end =
|
|
84
|
+
start = from_utc_iso_format(cfg.manual.q_from) if cfg.manual.q_from else None
|
|
85
|
+
end = from_utc_iso_format(cfg.manual.until) if cfg.manual.until else None
|
|
89
86
|
return ManualStatusProvider(
|
|
90
87
|
component_status=cfg.manual.component_status,
|
|
91
88
|
start=start,
|
|
@@ -47,7 +47,7 @@ rosa create cluster -y --cluster-name={{ cluster_name }} \
|
|
|
47
47
|
--service-cidr {{ cluster.network.service }} \
|
|
48
48
|
--pod-cidr {{ cluster.network.pod }} \
|
|
49
49
|
--host-prefix 23 \
|
|
50
|
-
--replicas
|
|
50
|
+
--replicas 3 \
|
|
51
51
|
--compute-machine-type {{ cluster.machine_pools[0].instance_type }} \
|
|
52
52
|
{% if cluster.spec.disable_user_workload_monitoring -%}
|
|
53
53
|
--disable-workload-monitoring \
|
|
@@ -55,4 +55,8 @@ rosa create cluster -y --cluster-name={{ cluster_name }} \
|
|
|
55
55
|
{% if cluster.spec.provision_shard_id -%}
|
|
56
56
|
--properties provision_shard_id:{{ cluster.spec.provision_shard_id }} \
|
|
57
57
|
{% endif -%}
|
|
58
|
+
{% if cluster.spec.fips -%}
|
|
59
|
+
--fips \
|
|
60
|
+
{% endif -%}
|
|
58
61
|
--channel-group {{ cluster.spec.channel }}
|
|
62
|
+
|
|
@@ -47,7 +47,7 @@ rosa create cluster --cluster-name={{ cluster_name }} \
|
|
|
47
47
|
--service-cidr {{ cluster.network.service }} \
|
|
48
48
|
--pod-cidr {{ cluster.network.pod }} \
|
|
49
49
|
--host-prefix 23 \
|
|
50
|
-
--replicas
|
|
50
|
+
--replicas 3 \
|
|
51
51
|
--compute-machine-type {{ cluster.machine_pools[0].instance_type }} \
|
|
52
52
|
{% if cluster.spec.private -%}
|
|
53
53
|
--private \
|
|
@@ -59,4 +59,7 @@ rosa create cluster --cluster-name={{ cluster_name }} \
|
|
|
59
59
|
{% if cluster.spec.provision_shard_id -%}
|
|
60
60
|
--properties provision_shard_id:{{ cluster.spec.provision_shard_id }} \
|
|
61
61
|
{% endif -%}
|
|
62
|
+
{% if cluster.spec.fips -%}
|
|
63
|
+
--fips \
|
|
64
|
+
{% endif -%}
|
|
62
65
|
--channel-group {{ cluster.spec.channel }}
|
|
@@ -108,14 +108,14 @@ class TemplateRenderingMR(MergeRequestBase):
|
|
|
108
108
|
if content.is_new:
|
|
109
109
|
gitlab_cli.create_file(
|
|
110
110
|
branch_name=self.branch,
|
|
111
|
-
file_path=
|
|
111
|
+
file_path=content.path.lstrip("/"),
|
|
112
112
|
commit_message="termplate rendering output",
|
|
113
113
|
content=content.content,
|
|
114
114
|
)
|
|
115
115
|
else:
|
|
116
116
|
gitlab_cli.update_file(
|
|
117
117
|
branch_name=self.branch,
|
|
118
|
-
file_path=
|
|
118
|
+
file_path=content.path.lstrip("/"),
|
|
119
119
|
commit_message="termplate rendering output",
|
|
120
120
|
content=content.content,
|
|
121
121
|
)
|
|
@@ -18,7 +18,7 @@ from reconcile.utils.secret_reader import SecretReaderBase
|
|
|
18
18
|
|
|
19
19
|
class TemplateData(BaseModel):
|
|
20
20
|
variables: dict[str, Any]
|
|
21
|
-
current: dict[str, Any] | None
|
|
21
|
+
current: dict[str, Any] | None = None
|
|
22
22
|
current_with_explicit_start: bool | None = False
|
|
23
23
|
|
|
24
24
|
|
|
@@ -26,7 +26,7 @@ class TemplatePatch(Protocol):
|
|
|
26
26
|
path: str
|
|
27
27
|
identifier: str | None
|
|
28
28
|
|
|
29
|
-
def
|
|
29
|
+
def model_dump(self) -> dict[str, str]: ...
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class Template(Protocol):
|
|
@@ -36,7 +36,7 @@ class Template(Protocol):
|
|
|
36
36
|
template: str
|
|
37
37
|
overwrite: bool | None
|
|
38
38
|
|
|
39
|
-
def
|
|
39
|
+
def model_dump(self) -> dict[str, str]: ...
|
|
40
40
|
|
|
41
41
|
@property
|
|
42
42
|
def patch(self) -> TemplatePatch | None:
|
reconcile/templating/renderer.py
CHANGED
|
@@ -33,6 +33,7 @@ from reconcile.utils import gql
|
|
|
33
33
|
from reconcile.utils.git import checkout, clone
|
|
34
34
|
from reconcile.utils.gql import GqlApi
|
|
35
35
|
from reconcile.utils.jinja2.utils import TemplateRenderOptions, process_jinja2_template
|
|
36
|
+
from reconcile.utils.json import json_dumps
|
|
36
37
|
from reconcile.utils.ruamel import create_ruamel_instance
|
|
37
38
|
from reconcile.utils.runtime.integration import (
|
|
38
39
|
PydanticRunParams,
|
|
@@ -95,18 +96,16 @@ class LocalFilePersistence(FilePersistence):
|
|
|
95
96
|
This class provides a simple file persistence implementation for local files.
|
|
96
97
|
"""
|
|
97
98
|
|
|
98
|
-
def __init__(self, dry_run: bool,
|
|
99
|
+
def __init__(self, dry_run: bool, app_interface_root_path: str) -> None:
|
|
99
100
|
super().__init__(dry_run)
|
|
100
|
-
|
|
101
|
-
raise ValueError("app_interface_data_path should end with /data")
|
|
102
|
-
self.app_interface_data_path = app_interface_data_path
|
|
101
|
+
self.app_interface_root_path = app_interface_root_path
|
|
103
102
|
|
|
104
103
|
def read(self, path: str) -> str | None:
|
|
105
|
-
return self._read_local_file(join_path(self.
|
|
104
|
+
return self._read_local_file(join_path(self.app_interface_root_path, path))
|
|
106
105
|
|
|
107
106
|
def flush(self) -> None:
|
|
108
107
|
for output in self.outputs:
|
|
109
|
-
filepath = Path(join_path(self.
|
|
108
|
+
filepath = Path(join_path(self.app_interface_root_path, output.path))
|
|
110
109
|
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
111
110
|
filepath.write_text(output.content, encoding="utf-8")
|
|
112
111
|
|
|
@@ -159,7 +158,7 @@ class ClonedRepoGitlabPersistence(FilePersistence):
|
|
|
159
158
|
self, dry_run: bool, local_path: str, vcs: VCS, mr_manager: MergeRequestManager
|
|
160
159
|
):
|
|
161
160
|
super().__init__(dry_run)
|
|
162
|
-
self.local_path =
|
|
161
|
+
self.local_path = local_path
|
|
163
162
|
self.vcs = vcs
|
|
164
163
|
self.mr_manager = mr_manager
|
|
165
164
|
|
|
@@ -217,7 +216,7 @@ def unpack_static_variables(
|
|
|
217
216
|
each: dict[str, Any],
|
|
218
217
|
) -> dict:
|
|
219
218
|
return {
|
|
220
|
-
k: json.loads(process_jinja2_template(body=
|
|
219
|
+
k: json.loads(process_jinja2_template(body=json_dumps(v), vars={"each": each}))
|
|
221
220
|
for k, v in (collection_variables.static or {}).items()
|
|
222
221
|
}
|
|
223
222
|
|
|
@@ -240,8 +239,8 @@ def unpack_dynamic_variables(
|
|
|
240
239
|
|
|
241
240
|
class TemplateRendererIntegrationParams(PydanticRunParams):
|
|
242
241
|
clone_repo: bool = False
|
|
243
|
-
|
|
244
|
-
template_collection_name: str | None
|
|
242
|
+
app_interface_root_path: str | None = None
|
|
243
|
+
template_collection_name: str | None = None
|
|
245
244
|
|
|
246
245
|
|
|
247
246
|
def join_path(base: str, sub: str) -> str:
|
|
@@ -367,10 +366,10 @@ class TemplateRendererIntegration(QontractReconcileIntegration):
|
|
|
367
366
|
persistence: FilePersistence
|
|
368
367
|
ruaml_instance = create_ruamel_instance(explicit_start=True)
|
|
369
368
|
|
|
370
|
-
if not self.params.clone_repo and self.params.
|
|
369
|
+
if not self.params.clone_repo and self.params.app_interface_root_path:
|
|
371
370
|
persistence = LocalFilePersistence(
|
|
372
371
|
dry_run=dry_run,
|
|
373
|
-
|
|
372
|
+
app_interface_root_path=self.params.app_interface_root_path,
|
|
374
373
|
)
|
|
375
374
|
self.reconcile(persistence, ruaml_instance)
|
|
376
375
|
|
|
@@ -411,4 +410,4 @@ class TemplateRendererIntegration(QontractReconcileIntegration):
|
|
|
411
410
|
self.reconcile(persistence, ruaml_instance)
|
|
412
411
|
|
|
413
412
|
else:
|
|
414
|
-
raise ValueError("App-interface-
|
|
413
|
+
raise ValueError("App-interface-root-path must be set")
|
|
@@ -9,6 +9,7 @@ from typing import Any
|
|
|
9
9
|
|
|
10
10
|
from reconcile import queries
|
|
11
11
|
from reconcile.status import ExitCodes
|
|
12
|
+
from reconcile.typed_queries.external_resources import get_settings
|
|
12
13
|
from reconcile.utils import dnsutils
|
|
13
14
|
from reconcile.utils.aws_api import AWSApi
|
|
14
15
|
from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
|
|
@@ -227,13 +228,18 @@ def run(
|
|
|
227
228
|
f"No participating AWS accounts found, consider disabling this integration, account name: {account_name}"
|
|
228
229
|
)
|
|
229
230
|
return
|
|
230
|
-
|
|
231
|
+
try:
|
|
232
|
+
default_tags = get_settings().default_tags
|
|
233
|
+
except ValueError:
|
|
234
|
+
# no external resources settings found
|
|
235
|
+
default_tags = None
|
|
231
236
|
ts = Terrascript(
|
|
232
237
|
QONTRACT_INTEGRATION,
|
|
233
238
|
"",
|
|
234
239
|
thread_pool_size,
|
|
235
240
|
participating_accounts,
|
|
236
241
|
settings=settings,
|
|
242
|
+
default_tags=default_tags,
|
|
237
243
|
)
|
|
238
244
|
|
|
239
245
|
desired_state = build_desired_state(zones, all_accounts, settings)
|
|
@@ -155,7 +155,7 @@ class TerraformCloudflareDNSIntegration(
|
|
|
155
155
|
|
|
156
156
|
accts_per_zone = []
|
|
157
157
|
for zone in query_zones.zones or []:
|
|
158
|
-
acct = zone.account.
|
|
158
|
+
acct = zone.account.model_dump(by_alias=True)
|
|
159
159
|
acct["name"] = f"{zone.account.name}-{zone.identifier}"
|
|
160
160
|
accts_per_zone.append(acct)
|
|
161
161
|
|
|
@@ -369,11 +369,11 @@ def cloudflare_dns_zone_to_external_resource(
|
|
|
369
369
|
provision_provider=DEFAULT_PROVISIONER_PROVIDER,
|
|
370
370
|
provisioner={"name": f"{zone.account.name}-{zone.identifier}"},
|
|
371
371
|
namespace=DEFAULT_NAMESPACE,
|
|
372
|
-
resource=zone.
|
|
372
|
+
resource=zone.model_dump(by_alias=True, exclude=DEFAULT_EXCLUDE_KEY),
|
|
373
373
|
)
|
|
374
374
|
external_resource_spec.resource["provider"] = DEFAULT_PROVIDER
|
|
375
375
|
external_resource_spec.resource["records"] = [
|
|
376
|
-
record.
|
|
376
|
+
record.model_dump(by_alias=True) for record in zone.records or []
|
|
377
377
|
]
|
|
378
378
|
external_resource_specs.append(external_resource_spec)
|
|
379
379
|
return external_resource_specs
|
|
@@ -168,7 +168,7 @@ def _build_oc_resources(
|
|
|
168
168
|
internal=internal,
|
|
169
169
|
)
|
|
170
170
|
|
|
171
|
-
namespace_mapping = [ns.
|
|
171
|
+
namespace_mapping = [ns.model_dump() for ns in cloudflare_namespaces]
|
|
172
172
|
|
|
173
173
|
state_specs = init_specs_to_fetch(
|
|
174
174
|
ri, oc_map, namespaces=namespace_mapping, override_managed_types=["Secret"]
|
|
@@ -338,7 +338,7 @@ def run(
|
|
|
338
338
|
)
|
|
339
339
|
|
|
340
340
|
if not cloudflare_namespaces:
|
|
341
|
-
logging.
|
|
341
|
+
logging.debug("No cloudflare namespaces were detected, nothing to do.")
|
|
342
342
|
sys.exit(ExitCodes.SUCCESS)
|
|
343
343
|
|
|
344
344
|
# Build Cloudflare clients
|
|
@@ -351,7 +351,7 @@ def run(
|
|
|
351
351
|
spec
|
|
352
352
|
for namespace in query_resources.namespaces
|
|
353
353
|
for spec in get_external_resource_specs(
|
|
354
|
-
namespace.
|
|
354
|
+
namespace.model_dump(by_alias=True), PROVIDER_CLOUDFLARE
|
|
355
355
|
)
|
|
356
356
|
if not selected_account or spec.provisioner_name == selected_account
|
|
357
357
|
]
|
|
@@ -383,7 +383,7 @@ def run(
|
|
|
383
383
|
QONTRACT_INTEGRATION_VERSION,
|
|
384
384
|
QONTRACT_TF_PREFIX,
|
|
385
385
|
[
|
|
386
|
-
acct.
|
|
386
|
+
acct.model_dump(by_alias=True) # convert CloudflareAccountV1 to dict
|
|
387
387
|
for acct in query_accounts.accounts or []
|
|
388
388
|
if acct.name in cf_clients.dump() # use only if it is a registered client
|
|
389
389
|
],
|
|
@@ -442,4 +442,4 @@ def _get_cloudflare_desired_state() -> tuple[
|
|
|
442
442
|
def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
|
|
443
443
|
desired_state = _get_cloudflare_desired_state()
|
|
444
444
|
|
|
445
|
-
return {state
|
|
445
|
+
return {str(state): state.model_dump() for state in desired_state}
|
|
@@ -88,7 +88,7 @@ class TerraformCloudflareUsers(
|
|
|
88
88
|
if not settings.settings:
|
|
89
89
|
raise RuntimeError("App interface setting not defined")
|
|
90
90
|
|
|
91
|
-
early_exit_desired_state = cloudflare_roles.
|
|
91
|
+
early_exit_desired_state = cloudflare_roles.model_dump()
|
|
92
92
|
early_exit_desired_state.update({
|
|
93
93
|
CLOUDFLARE_EMAIL_DOMAIN_ALLOW_LIST_KEY: settings.settings
|
|
94
94
|
})
|
|
@@ -149,7 +149,8 @@ class TerraformCloudflareUsers(
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
accounts = [
|
|
152
|
-
acct.
|
|
152
|
+
acct.model_dump(by_alias=True)
|
|
153
|
+
for _, acct in account_names_to_account.items()
|
|
153
154
|
]
|
|
154
155
|
|
|
155
156
|
self._run_terraform(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from collections.abc import Callable
|
|
3
|
-
from datetime import UTC, datetime
|
|
4
3
|
from typing import Any
|
|
5
4
|
|
|
6
5
|
import jinja2
|
|
@@ -12,12 +11,16 @@ from reconcile.gql_definitions.terraform_init.aws_accounts import (
|
|
|
12
11
|
from reconcile.terraform_init.merge_request import Renderer, create_parser
|
|
13
12
|
from reconcile.terraform_init.merge_request_manager import MergeRequestManager, MrData
|
|
14
13
|
from reconcile.typed_queries.app_interface_repo_url import get_app_interface_repo_url
|
|
14
|
+
from reconcile.typed_queries.aws_account_tags import get_aws_account_tags
|
|
15
|
+
from reconcile.typed_queries.external_resources import get_settings
|
|
15
16
|
from reconcile.typed_queries.github_orgs import get_github_orgs
|
|
16
17
|
from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
|
|
17
18
|
from reconcile.utils import gql
|
|
18
19
|
from reconcile.utils.aws_api_typed.api import AWSApi, AWSStaticCredentials
|
|
20
|
+
from reconcile.utils.datetime_util import utc_now
|
|
19
21
|
from reconcile.utils.defer import defer
|
|
20
22
|
from reconcile.utils.disabled_integrations import integration_is_enabled
|
|
23
|
+
from reconcile.utils.gql import GqlApi
|
|
21
24
|
from reconcile.utils.runtime.integration import (
|
|
22
25
|
PydanticRunParams,
|
|
23
26
|
QontractReconcileIntegration,
|
|
@@ -31,10 +34,16 @@ QONTRACT_INTEGRATION_VERSION = make_semver(1, 0, 0)
|
|
|
31
34
|
|
|
32
35
|
|
|
33
36
|
class TerraformInitIntegrationParams(PydanticRunParams):
|
|
34
|
-
account_name: str | None
|
|
37
|
+
account_name: str | None = None
|
|
35
38
|
# To avoid the accidental deletion of the resource file, explicitly set the
|
|
36
39
|
# qontract.cli option in the integration extraArgs!
|
|
37
40
|
state_tmpl_resource: str = "/terraform-init/terraform-state.yml"
|
|
41
|
+
cloudformation_template_resource: str = (
|
|
42
|
+
"/terraform-init/terraform-state-s3-bucket.yaml"
|
|
43
|
+
)
|
|
44
|
+
cloudformation_import_template_resource: str = (
|
|
45
|
+
"/terraform-init/terraform-state-s3-bucket-import.yaml"
|
|
46
|
+
)
|
|
38
47
|
template_collection_root_path: str = "data/templating/collections/terraform-init"
|
|
39
48
|
|
|
40
49
|
|
|
@@ -55,25 +64,35 @@ class TerraformInitIntegration(
|
|
|
55
64
|
query_func = gql.get_api().query
|
|
56
65
|
return {
|
|
57
66
|
"accounts": [
|
|
58
|
-
account.
|
|
67
|
+
account.model_dump() for account in self.get_aws_accounts(query_func)
|
|
59
68
|
],
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
def get_aws_accounts(
|
|
63
72
|
self, query_func: Callable, account_name: str | None = None
|
|
64
73
|
) -> list[AWSAccountV1]:
|
|
65
|
-
"""Return all AWS accounts with terraform username
|
|
74
|
+
"""Return all AWS accounts with terraform username."""
|
|
66
75
|
return [
|
|
67
76
|
account
|
|
68
77
|
for account in aws_accounts_query(query_func).accounts or []
|
|
69
78
|
if integration_is_enabled(self.name, account)
|
|
70
79
|
and (not account_name or account.name == account_name)
|
|
71
80
|
and account.terraform_username
|
|
72
|
-
and not account.terraform_state
|
|
73
81
|
]
|
|
74
82
|
|
|
83
|
+
@staticmethod
|
|
84
|
+
def get_default_tags(gql_api: GqlApi) -> dict[str, str]:
|
|
85
|
+
try:
|
|
86
|
+
return get_settings(gql_api.query).default_tags
|
|
87
|
+
except ValueError:
|
|
88
|
+
# no settings found
|
|
89
|
+
return {}
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
75
92
|
def render_state_collection(
|
|
76
|
-
|
|
93
|
+
template: str,
|
|
94
|
+
bucket_name: str,
|
|
95
|
+
account: AWSAccountV1,
|
|
77
96
|
) -> str:
|
|
78
97
|
return jinja2.Template(
|
|
79
98
|
template,
|
|
@@ -85,24 +104,114 @@ class TerraformInitIntegration(
|
|
|
85
104
|
"account_name": account.name,
|
|
86
105
|
"bucket_name": bucket_name,
|
|
87
106
|
"region": account.resources_default_region,
|
|
88
|
-
"timestamp": int(
|
|
107
|
+
"timestamp": int(utc_now().timestamp()),
|
|
89
108
|
})
|
|
90
109
|
|
|
91
110
|
def reconcile_account(
|
|
92
111
|
self,
|
|
93
|
-
|
|
112
|
+
aws_api: AWSApi,
|
|
113
|
+
merge_request_manager: MergeRequestManager,
|
|
114
|
+
dry_run: bool,
|
|
115
|
+
account: AWSAccountV1,
|
|
116
|
+
state_template: str,
|
|
117
|
+
cloudformation_template: str,
|
|
118
|
+
cloudformation_import_template: str,
|
|
119
|
+
default_tags: dict[str, str],
|
|
120
|
+
) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Reconcile the terraform state for a given account.
|
|
123
|
+
|
|
124
|
+
Create S3 bucket via CloudFormation and Merge Request to update template file on init.
|
|
125
|
+
Import existing bucket if it exists but not managed by CloudFormation.
|
|
126
|
+
Update CloudFormation stack if tags or template body differ.
|
|
127
|
+
|
|
128
|
+
CloudFormation stack name and bucket name is `terraform-<account_name>`.
|
|
129
|
+
`cloudformation_import_template` should be minimal template with identifier fields only.
|
|
130
|
+
`cloudformation_template` should be the full template.
|
|
131
|
+
Use import template to import then update stack to match full template.
|
|
132
|
+
This will ensure imported resources match CloudFormation template.
|
|
133
|
+
And all desired changes in full template are applied.
|
|
134
|
+
Both templates must have BucketName in Parameters.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
aws_api: AWSApi: AWS API client for the target account.
|
|
138
|
+
merge_request_manager: MergeRequestManager: Manager to handle merge requests.
|
|
139
|
+
dry_run: bool: If True, do not make any changes.
|
|
140
|
+
account: AWSAccountV1: The AWS account to reconcile.
|
|
141
|
+
state_template: str: Jinja2 template for the Terraform state configuration.
|
|
142
|
+
cloudformation_template: str: CloudFormation template to create the S3 bucket.
|
|
143
|
+
cloudformation_import_template: str: CloudFormation template to import existing S3 bucket.
|
|
144
|
+
default_tags: dict[str, str]: Default tags to apply to the CloudFormation stack.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
None
|
|
148
|
+
"""
|
|
149
|
+
bucket_name = (
|
|
150
|
+
account.terraform_state.bucket
|
|
151
|
+
if account.terraform_state
|
|
152
|
+
else f"terraform-{account.name}"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
tags = default_tags | get_aws_account_tags(account.organization)
|
|
156
|
+
|
|
157
|
+
if account.terraform_state is None:
|
|
158
|
+
return self._provision_terraform_state(
|
|
159
|
+
aws_api=aws_api,
|
|
160
|
+
merge_request_manager=merge_request_manager,
|
|
161
|
+
dry_run=dry_run,
|
|
162
|
+
account=account,
|
|
163
|
+
bucket_name=bucket_name,
|
|
164
|
+
cloudformation_template=cloudformation_template,
|
|
165
|
+
state_template=state_template,
|
|
166
|
+
tags=tags,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
stack = aws_api.cloudformation.get_stack(stack_name=bucket_name)
|
|
170
|
+
|
|
171
|
+
if stack is None:
|
|
172
|
+
return self._import_cloudformation_stack(
|
|
173
|
+
aws_api=aws_api,
|
|
174
|
+
dry_run=dry_run,
|
|
175
|
+
bucket_name=bucket_name,
|
|
176
|
+
cloudformation_import_template=cloudformation_import_template,
|
|
177
|
+
cloudformation_template=cloudformation_template,
|
|
178
|
+
tags=tags,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return self._reconcile_cloudformation_stack(
|
|
182
|
+
aws_api=aws_api,
|
|
183
|
+
dry_run=dry_run,
|
|
184
|
+
bucket_name=bucket_name,
|
|
185
|
+
cloudformation_template=cloudformation_template,
|
|
186
|
+
tags=tags,
|
|
187
|
+
current_tags={tag["Key"]: tag["Value"] for tag in stack.get("Tags", [])},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def _provision_terraform_state(
|
|
191
|
+
self,
|
|
192
|
+
aws_api: AWSApi,
|
|
94
193
|
merge_request_manager: MergeRequestManager,
|
|
95
194
|
dry_run: bool,
|
|
96
|
-
state_collection: str,
|
|
97
|
-
bucket_name: str,
|
|
98
195
|
account: AWSAccountV1,
|
|
196
|
+
bucket_name: str,
|
|
197
|
+
cloudformation_template: str,
|
|
198
|
+
state_template: str,
|
|
199
|
+
tags: dict[str, str],
|
|
99
200
|
) -> None:
|
|
100
201
|
logging.info("Creating bucket '%s' for account '%s'", bucket_name, account.name)
|
|
101
202
|
if not dry_run:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
203
|
+
aws_api.cloudformation.create_stack(
|
|
204
|
+
stack_name=bucket_name,
|
|
205
|
+
change_set_name=f"create-{bucket_name}",
|
|
206
|
+
template_body=cloudformation_template,
|
|
207
|
+
parameters={"BucketName": bucket_name},
|
|
208
|
+
tags=tags,
|
|
105
209
|
)
|
|
210
|
+
state_collection = self.render_state_collection(
|
|
211
|
+
template=state_template,
|
|
212
|
+
bucket_name=bucket_name,
|
|
213
|
+
account=account,
|
|
214
|
+
)
|
|
106
215
|
merge_request_manager.create_merge_request(
|
|
107
216
|
data=MrData(
|
|
108
217
|
account=account.name,
|
|
@@ -111,6 +220,55 @@ class TerraformInitIntegration(
|
|
|
111
220
|
)
|
|
112
221
|
)
|
|
113
222
|
|
|
223
|
+
@staticmethod
|
|
224
|
+
def _import_cloudformation_stack(
|
|
225
|
+
aws_api: AWSApi,
|
|
226
|
+
dry_run: bool,
|
|
227
|
+
bucket_name: str,
|
|
228
|
+
cloudformation_import_template: str,
|
|
229
|
+
cloudformation_template: str,
|
|
230
|
+
tags: dict[str, str],
|
|
231
|
+
) -> None:
|
|
232
|
+
logging.info("Importing existing bucket %s", bucket_name)
|
|
233
|
+
if not dry_run:
|
|
234
|
+
aws_api.cloudformation.create_stack(
|
|
235
|
+
stack_name=bucket_name,
|
|
236
|
+
change_set_name=f"import-{bucket_name}",
|
|
237
|
+
template_body=cloudformation_import_template,
|
|
238
|
+
parameters={"BucketName": bucket_name},
|
|
239
|
+
tags=tags,
|
|
240
|
+
)
|
|
241
|
+
logging.info("Syncing stack %s after import", bucket_name)
|
|
242
|
+
aws_api.cloudformation.update_stack(
|
|
243
|
+
stack_name=bucket_name,
|
|
244
|
+
template_body=cloudformation_template,
|
|
245
|
+
parameters={"BucketName": bucket_name},
|
|
246
|
+
tags=tags,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def _reconcile_cloudformation_stack(
|
|
251
|
+
aws_api: AWSApi,
|
|
252
|
+
dry_run: bool,
|
|
253
|
+
bucket_name: str,
|
|
254
|
+
cloudformation_template: str,
|
|
255
|
+
tags: dict[str, str],
|
|
256
|
+
current_tags: dict[str, str],
|
|
257
|
+
) -> None:
|
|
258
|
+
if (
|
|
259
|
+
current_tags != tags
|
|
260
|
+
or aws_api.cloudformation.get_template_body(stack_name=bucket_name)
|
|
261
|
+
!= cloudformation_template
|
|
262
|
+
):
|
|
263
|
+
logging.info("Updating stack %s", bucket_name)
|
|
264
|
+
if not dry_run:
|
|
265
|
+
aws_api.cloudformation.update_stack(
|
|
266
|
+
stack_name=bucket_name,
|
|
267
|
+
template_body=cloudformation_template,
|
|
268
|
+
parameters={"BucketName": bucket_name},
|
|
269
|
+
tags=tags,
|
|
270
|
+
)
|
|
271
|
+
|
|
114
272
|
@defer
|
|
115
273
|
def run(self, dry_run: bool, defer: Callable | None = None) -> None:
|
|
116
274
|
"""Run the integration."""
|
|
@@ -144,6 +302,14 @@ class TerraformInitIntegration(
|
|
|
144
302
|
state_template = gql_api.get_resource(path=self.params.state_tmpl_resource)[
|
|
145
303
|
"content"
|
|
146
304
|
]
|
|
305
|
+
cloudformation_template = gql_api.get_resource(
|
|
306
|
+
path=self.params.cloudformation_template_resource
|
|
307
|
+
)["content"]
|
|
308
|
+
cloudformation_import_template = gql_api.get_resource(
|
|
309
|
+
path=self.params.cloudformation_import_template_resource
|
|
310
|
+
)["content"]
|
|
311
|
+
default_tags = self.get_default_tags(gql_api)
|
|
312
|
+
|
|
147
313
|
for account in accounts:
|
|
148
314
|
secret = self.secret_reader.read_all_secret(account.automation_token)
|
|
149
315
|
with AWSApi(
|
|
@@ -153,15 +319,13 @@ class TerraformInitIntegration(
|
|
|
153
319
|
region=account.resources_default_region,
|
|
154
320
|
)
|
|
155
321
|
) as account_aws_api:
|
|
156
|
-
bucket_name = f"terraform-{account.name}"
|
|
157
|
-
state_collection = self.render_state_collection(
|
|
158
|
-
state_template, bucket_name, account
|
|
159
|
-
)
|
|
160
322
|
self.reconcile_account(
|
|
161
|
-
account_aws_api,
|
|
162
|
-
merge_request_manager,
|
|
163
|
-
dry_run,
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
323
|
+
aws_api=account_aws_api,
|
|
324
|
+
merge_request_manager=merge_request_manager,
|
|
325
|
+
dry_run=dry_run,
|
|
326
|
+
account=account,
|
|
327
|
+
state_template=state_template,
|
|
328
|
+
cloudformation_template=cloudformation_template,
|
|
329
|
+
cloudformation_import_template=cloudformation_import_template,
|
|
330
|
+
default_tags=default_tags,
|
|
167
331
|
)
|