From 17dffb6c5192c03eb6257dd062d8e8ab2a022c30 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 5 May 2022 06:08:22 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/frontend.gitlab-ci.yml | 4 +- .gitlab/ci/rails.gitlab-ci.yml | 4 +- .rubocop_todo.yml | 6 - .rubocop_todo/layout/argument_alignment.yml | 1 - .../lint/missing_cop_enable_directive.yml | 1 - .../rails/skips_model_validations.yml | 749 ++++++++++++++++ .rubocop_todo/rspec/verified_doubles.yml | 1 - Gemfile.lock | 2 +- .../pages/projects/serverless/index.js | 3 - .../serverless/components/area.vue | 145 ---- .../serverless/components/empty_state.vue | 39 - .../serverless/components/environment_row.vue | 65 -- .../components/function_details.vue | 94 -- .../serverless/components/function_row.vue | 77 -- .../serverless/components/functions.vue | 139 --- .../components/missing_prometheus.vue | 57 -- .../serverless/components/pod_box.vue | 36 - .../javascripts/serverless/components/url.vue | 28 - .../javascripts/serverless/constants.js | 10 - .../javascripts/serverless/event_hub.js | 3 - .../serverless/serverless_bundle.js | 67 -- .../javascripts/serverless/store/actions.js | 131 --- .../javascripts/serverless/store/getters.js | 7 - .../javascripts/serverless/store/index.js | 18 - .../serverless/store/mutation_types.js | 11 - .../javascripts/serverless/store/mutations.js | 49 -- .../javascripts/serverless/store/state.js | 22 - app/assets/javascripts/serverless/utils.js | 20 - .../assignees/sidebar_participant.vue | 20 +- app/controllers/concerns/uploads_actions.rb | 14 +- .../serverless/functions_controller.rb | 80 -- .../projects/uploads_controller.rb | 4 + app/controllers/projects_controller.rb | 1 + .../projects/serverless/functions_finder.rb | 153 ---- app/helpers/projects_helper.rb | 1 + app/models/project.rb | 1 + .../serverless/functions/index.html.haml | 17 - .../serverless/functions/show.html.haml | 19 - .../ops/deprecated_serverless.yml | 8 - config/routes/project.rb | 9 - data/removals/15_0/15-0-serverless.yml | 17 + ...h_checks_on_uploads_to_project_settings.rb | 9 + db/schema_migrations/20220324091224 | 1 + db/structure.sql | 1 + doc/administration/git_protocol.md | 2 +- doc/administration/gitaly/configure_gitaly.md | 9 +- doc/administration/gitaly/faq.md | 1 - doc/administration/gitaly/index.md | 195 +---- doc/administration/gitaly/monitoring.md | 191 ++++ doc/administration/gitaly/praefect.md | 1 - doc/administration/gitaly/recovery.md | 1 - doc/administration/gitaly/reference.md | 3 +- doc/administration/gitaly/troubleshooting.md | 1 - doc/administration/logs.md | 2 +- .../monitoring/prometheus/gitlab_metrics.md | 2 +- .../postgresql_versions.md | 1 + doc/api/graphql/reference/index.md | 2 +- .../deprecation_guidelines/index.md | 49 ++ doc/update/index.md | 6 + doc/update/removals.md | 15 + doc/user/clusters/applications.md | 15 - doc/user/infrastructure/clusters/index.md | 1 - doc/user/project/clusters/serverless/aws.md | 504 +---------- .../img/function-details-loaded_v14_0.png | Bin 21864 -> 0 bytes .../serverless/img/function-endpoint.png | Bin 14641 -> 0 bytes .../serverless/img/function-execution.png | Bin 73788 -> 0 bytes .../serverless/img/function-list_v12_7.png | Bin 18551 -> 0 bytes .../serverless/img/sam-api-endpoint.png | Bin 29991 -> 0 bytes .../serverless/img/sam-complete-raw.png | Bin 38847 -> 0 bytes .../serverless/img/serverless-page_v14_0.png | Bin 18188 -> 0 bytes doc/user/project/clusters/serverless/index.md | 816 +----------------- lib/api/entities/project.rb | 1 + lib/api/helpers/projects_helpers.rb | 2 + .../ci/templates/Serverless.gitlab-ci.yml | 35 - .../import_export/project/import_export.yml | 1 + .../projects/menus/infrastructure_menu.rb | 14 - locale/gitlab.pot | 78 -- .../groups/uploads_controller_spec.rb | 163 ++++ .../serverless/functions_controller_spec.rb | 341 -------- .../projects/uploads_controller_spec.rb | 235 +++++ spec/controllers/projects_controller_spec.rb | 4 +- spec/features/monitor_sidebar_link_spec.rb | 5 - .../projects/serverless/functions_spec.rb | 88 -- .../serverless/functions_finder_spec.rb | 185 ---- .../__snapshots__/empty_state_spec.js.snap | 22 - .../serverless/components/area_spec.js | 121 --- .../serverless/components/empty_state_spec.js | 25 - .../components/environment_row_spec.js | 68 -- .../components/function_details_spec.js | 100 --- .../components/function_row_spec.js | 34 - .../serverless/components/functions_spec.js | 86 -- .../components/missing_prometheus_spec.js | 38 - .../serverless/components/pod_box_spec.js | 22 - .../serverless/components/url_spec.js | 26 - spec/frontend/serverless/mock_data.js | 145 ---- .../frontend/serverless/store/actions_spec.js | 80 -- .../frontend/serverless/store/getters_spec.js | 43 - .../serverless/store/mutations_spec.js | 86 -- spec/frontend/serverless/utils.js | 17 - .../legacy_github_import/importer_spec.rb | 3 +- .../menus/infrastructure_menu_spec.rb | 36 +- spec/models/project_spec.rb | 3 + spec/requests/api/projects_spec.rb | 14 + .../navbar_structure_context.rb | 1 - .../nav/sidebar/_project.html.haml_spec.rb | 18 - 105 files changed, 1526 insertions(+), 4575 deletions(-) create mode 100644 .rubocop_todo/rails/skips_model_validations.yml delete mode 100644 app/assets/javascripts/pages/projects/serverless/index.js delete mode 100644 app/assets/javascripts/serverless/components/area.vue delete mode 100644 app/assets/javascripts/serverless/components/empty_state.vue delete mode 100644 app/assets/javascripts/serverless/components/environment_row.vue delete mode 100644 app/assets/javascripts/serverless/components/function_details.vue delete mode 100644 app/assets/javascripts/serverless/components/function_row.vue delete mode 100644 app/assets/javascripts/serverless/components/functions.vue delete mode 100644 app/assets/javascripts/serverless/components/missing_prometheus.vue delete mode 100644 app/assets/javascripts/serverless/components/pod_box.vue delete mode 100644 app/assets/javascripts/serverless/components/url.vue delete mode 100644 app/assets/javascripts/serverless/constants.js delete mode 100644 app/assets/javascripts/serverless/event_hub.js delete mode 100644 app/assets/javascripts/serverless/serverless_bundle.js delete mode 100644 app/assets/javascripts/serverless/store/actions.js delete mode 100644 app/assets/javascripts/serverless/store/getters.js delete mode 100644 app/assets/javascripts/serverless/store/index.js delete mode 100644 app/assets/javascripts/serverless/store/mutation_types.js delete mode 100644 app/assets/javascripts/serverless/store/mutations.js delete mode 100644 app/assets/javascripts/serverless/store/state.js delete mode 100644 app/assets/javascripts/serverless/utils.js delete mode 100644 app/controllers/projects/serverless/functions_controller.rb delete mode 100644 app/finders/projects/serverless/functions_finder.rb delete mode 100644 app/views/projects/serverless/functions/index.html.haml delete mode 100644 app/views/projects/serverless/functions/show.html.haml delete mode 100644 config/feature_flags/ops/deprecated_serverless.yml create mode 100644 data/removals/15_0/15-0-serverless.yml create mode 100644 db/migrate/20220324091224_add_enforce_auth_checks_on_uploads_to_project_settings.rb create mode 100644 db/schema_migrations/20220324091224 create mode 100644 doc/administration/gitaly/monitoring.md delete mode 100644 doc/user/project/clusters/serverless/img/function-details-loaded_v14_0.png delete mode 100644 doc/user/project/clusters/serverless/img/function-endpoint.png delete mode 100644 doc/user/project/clusters/serverless/img/function-execution.png delete mode 100644 doc/user/project/clusters/serverless/img/function-list_v12_7.png delete mode 100644 doc/user/project/clusters/serverless/img/sam-api-endpoint.png delete mode 100644 doc/user/project/clusters/serverless/img/sam-complete-raw.png delete mode 100644 doc/user/project/clusters/serverless/img/serverless-page_v14_0.png delete mode 100644 lib/gitlab/ci/templates/Serverless.gitlab-ci.yml delete mode 100644 spec/controllers/projects/serverless/functions_controller_spec.rb delete mode 100644 spec/features/projects/serverless/functions_spec.rb delete mode 100644 spec/finders/projects/serverless/functions_finder_spec.rb delete mode 100644 spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap delete mode 100644 spec/frontend/serverless/components/area_spec.js delete mode 100644 spec/frontend/serverless/components/empty_state_spec.js delete mode 100644 spec/frontend/serverless/components/environment_row_spec.js delete mode 100644 spec/frontend/serverless/components/function_details_spec.js delete mode 100644 spec/frontend/serverless/components/function_row_spec.js delete mode 100644 spec/frontend/serverless/components/functions_spec.js delete mode 100644 spec/frontend/serverless/components/missing_prometheus_spec.js delete mode 100644 spec/frontend/serverless/components/pod_box_spec.js delete mode 100644 spec/frontend/serverless/components/url_spec.js delete mode 100644 spec/frontend/serverless/mock_data.js delete mode 100644 spec/frontend/serverless/store/actions_spec.js delete mode 100644 spec/frontend/serverless/store/getters_spec.js delete mode 100644 spec/frontend/serverless/store/mutations_spec.js delete mode 100644 spec/frontend/serverless/utils.js diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 6e84d4f3914..4b1194d0fbd 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -280,7 +280,9 @@ coverage-frontend: paths: - coverage-frontend/ reports: - cobertura: coverage-frontend/cobertura-coverage.xml + coverage_report: + coverage_format: cobertura + path: coverage-frontend/cobertura-coverage.xml .qa-frontend-node: extends: diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index cbedd83ac95..b4793f91d36 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -610,7 +610,9 @@ rspec:coverage: - coverage/assets/ - coverage/lcov/ reports: - cobertura: coverage/coverage.xml + coverage_report: + coverage_format: cobertura + path: coverage/coverage.xml rspec:undercoverage: extends: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e9264ffc8d1..14bc169854a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -162,12 +162,6 @@ Rails/RakeEnvironment: Rails/RedundantForeignKey: Enabled: false -# Offense count: 1144 -# Configuration parameters: ForbiddenMethods, AllowedMethods. -# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all -Rails/SkipsModelValidations: - Enabled: false - # Offense count: 278 # Cop supports --auto-correct. Rails/SquishedSQLHeredocs: diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml index acb8e8157cc..d8d79d9fa90 100644 --- a/.rubocop_todo/layout/argument_alignment.yml +++ b/.rubocop_todo/layout/argument_alignment.yml @@ -688,7 +688,6 @@ Layout/ArgumentAlignment: - 'spec/finders/keys_finder_spec.rb' - 'spec/finders/merge_requests_finder_spec.rb' - 'spec/finders/personal_access_tokens_finder_spec.rb' - - 'spec/finders/projects/serverless/functions_finder_spec.rb' - 'spec/frontend/fixtures/issues.rb' - 'spec/frontend/fixtures/merge_requests.rb' - 'spec/frontend/fixtures/merge_requests_diffs.rb' diff --git a/.rubocop_todo/lint/missing_cop_enable_directive.yml b/.rubocop_todo/lint/missing_cop_enable_directive.yml index a8edf6c85dc..589f98c9e02 100644 --- a/.rubocop_todo/lint/missing_cop_enable_directive.yml +++ b/.rubocop_todo/lint/missing_cop_enable_directive.yml @@ -6,7 +6,6 @@ Lint/MissingCopEnableDirective: Exclude: - 'app/controllers/admin/users_controller.rb' - 'app/controllers/projects/forks_controller.rb' - - 'app/finders/projects/serverless/functions_finder.rb' - 'app/graphql/resolvers/group_issues_resolver.rb' - 'app/graphql/resolvers/issues_resolver.rb' - 'app/graphql/resolvers/project_members_resolver.rb' diff --git a/.rubocop_todo/rails/skips_model_validations.yml b/.rubocop_todo/rails/skips_model_validations.yml new file mode 100644 index 00000000000..9b908b7b997 --- /dev/null +++ b/.rubocop_todo/rails/skips_model_validations.yml @@ -0,0 +1,749 @@ +--- +Rails/SkipsModelValidations: + # Offense count: 1424 + # Temporarily disabled due to too many offenses + Enabled: false + Exclude: + - 'app/controllers/import/github_controller.rb' + - 'app/controllers/projects/environments_controller.rb' + - 'app/controllers/projects/notes_controller.rb' + - 'app/models/alert_management/alert.rb' + - 'app/models/analytics/cycle_analytics/aggregation.rb' + - 'app/models/chat_name.rb' + - 'app/models/ci/build.rb' + - 'app/models/ci/build_trace_chunks/database.rb' + - 'app/models/ci/build_trace_metadata.rb' + - 'app/models/ci/daily_build_group_report_result.rb' + - 'app/models/ci/deleted_object.rb' + - 'app/models/ci/namespace_mirror.rb' + - 'app/models/ci/pending_build.rb' + - 'app/models/ci/pipeline_schedule.rb' + - 'app/models/ci/processable.rb' + - 'app/models/ci/project_mirror.rb' + - 'app/models/ci/resource_group.rb' + - 'app/models/ci/runner.rb' + - 'app/models/ci/running_build.rb' + - 'app/models/ci/unit_test.rb' + - 'app/models/commit_status.rb' + - 'app/models/concerns/batch_nullify_dependent_associations.rb' + - 'app/models/concerns/board_recent_visit.rb' + - 'app/models/concerns/cache_markdown_field.rb' + - 'app/models/concerns/can_move_repository_storage.rb' + - 'app/models/concerns/cascading_namespace_setting_attribute.rb' + - 'app/models/concerns/counter_attribute.rb' + - 'app/models/concerns/deprecated_assignee.rb' + - 'app/models/concerns/file_store_mounter.rb' + - 'app/models/concerns/has_wiki_page_meta_attributes.rb' + - 'app/models/concerns/noteable.rb' + - 'app/models/concerns/packages/debian/distribution.rb' + - 'app/models/concerns/relative_positioning.rb' + - 'app/models/concerns/repository_storage_movable.rb' + - 'app/models/concerns/resolvable_note.rb' + - 'app/models/concerns/schedulable.rb' + - 'app/models/concerns/subscribable.rb' + - 'app/models/container_expiration_policy.rb' + - 'app/models/customer_relations/contact.rb' + - 'app/models/customer_relations/organization.rb' + - 'app/models/deployment.rb' + - 'app/models/diff_note_position.rb' + - 'app/models/environment.rb' + - 'app/models/gpg_key.rb' + - 'app/models/group.rb' + - 'app/models/group_import_state.rb' + - 'app/models/hooks/web_hook.rb' + - 'app/models/internal_id.rb' + - 'app/models/issue.rb' + - 'app/models/jira_import_state.rb' + - 'app/models/loose_foreign_keys/deleted_record.rb' + - 'app/models/merge_request.rb' + - 'app/models/merge_request/diff_commit_user.rb' + - 'app/models/merge_request_diff.rb' + - 'app/models/namespace.rb' + - 'app/models/note.rb' + - 'app/models/project.rb' + - 'app/models/project_authorization.rb' + - 'app/models/project_import_state.rb' + - 'app/models/project_statistics.rb' + - 'app/models/project_wiki.rb' + - 'app/models/projects/ci_feature_usage.rb' + - 'app/models/projects/repository_storage_move.rb' + - 'app/models/projects/topic.rb' + - 'app/models/raw_usage_data.rb' + - 'app/models/remote_mirror.rb' + - 'app/models/route.rb' + - 'app/models/todo.rb' + - 'app/models/u2f_registration.rb' + - 'app/models/user.rb' + - 'app/models/user_custom_attribute.rb' + - 'app/models/user_interacted_project.rb' + - 'app/services/boards/lists/base_destroy_service.rb' + - 'app/services/boards/lists/move_service.rb' + - 'app/services/bulk_create_integration_service.rb' + - 'app/services/bulk_update_integration_service.rb' + - 'app/services/ci/abort_pipelines_service.rb' + - 'app/services/ci/disable_user_pipeline_schedules_service.rb' + - 'app/services/ci/expire_pipeline_cache_service.rb' + - 'app/services/ci/job_artifacts/create_service.rb' + - 'app/services/ci/job_artifacts/destroy_batch_service.rb' + - 'app/services/ci/job_artifacts/expire_project_build_artifacts_service.rb' + - 'app/services/ci/job_artifacts/update_unknown_locked_status_service.rb' + - 'app/services/ci/test_failure_history_service.rb' + - 'app/services/ci/update_build_state_service.rb' + - 'app/services/ci/update_pending_build_service.rb' + - 'app/services/clusters/agent_tokens/track_usage_service.rb' + - 'app/services/clusters/agents/refresh_authorization_service.rb' + - 'app/services/clusters/integrations/prometheus_health_check_service.rb' + - 'app/services/deployments/archive_in_project_service.rb' + - 'app/services/event_create_service.rb' + - 'app/services/groups/transfer_service.rb' + - 'app/services/issuable_base_service.rb' + - 'app/services/issues/move_service.rb' + - 'app/services/issues/set_crm_contacts_service.rb' + - 'app/services/keys/expiry_notification_service.rb' + - 'app/services/keys/last_used_service.rb' + - 'app/services/labels/promote_service.rb' + - 'app/services/labels/transfer_service.rb' + - 'app/services/merge_requests/base_service.rb' + - 'app/services/merge_requests/bulk_remove_attention_requested_service.rb' + - 'app/services/merge_requests/cleanup_refs_service.rb' + - 'app/services/merge_requests/ff_merge_service.rb' + - 'app/services/merge_requests/handle_assignees_change_service.rb' + - 'app/services/merge_requests/merge_service.rb' + - 'app/services/merge_requests/rebase_service.rb' + - 'app/services/merge_requests/reopen_service.rb' + - 'app/services/milestones/promote_service.rb' + - 'app/services/milestones/transfer_service.rb' + - 'app/services/packages/composer/create_package_service.rb' + - 'app/services/packages/debian/generate_distribution_service.rb' + - 'app/services/packages/generic/create_package_file_service.rb' + - 'app/services/packages/mark_package_files_for_destruction_service.rb' + - 'app/services/packages/npm/create_tag_service.rb' + - 'app/services/packages/pypi/create_package_service.rb' + - 'app/services/packages/rubygems/create_dependencies_service.rb' + - 'app/services/personal_access_tokens/last_used_service.rb' + - 'app/services/projects/destroy_service.rb' + - 'app/services/projects/detect_repository_languages_service.rb' + - 'app/services/projects/move_deploy_keys_projects_service.rb' + - 'app/services/projects/move_forks_service.rb' + - 'app/services/projects/move_lfs_objects_projects_service.rb' + - 'app/services/projects/move_notification_settings_service.rb' + - 'app/services/projects/move_project_authorizations_service.rb' + - 'app/services/projects/move_project_group_links_service.rb' + - 'app/services/projects/move_project_members_service.rb' + - 'app/services/projects/move_users_star_projects_service.rb' + - 'app/services/projects/repository_languages_service.rb' + - 'app/services/projects/unlink_fork_service.rb' + - 'app/services/reset_project_cache_service.rb' + - 'app/services/spam/akismet_mark_as_spam_service.rb' + - 'app/services/spam/ham_service.rb' + - 'app/services/suggestions/apply_service.rb' + - 'app/services/suggestions/outdate_service.rb' + - 'app/services/users/activity_service.rb' + - 'app/services/users/migrate_to_ghost_user_service.rb' + - 'app/services/users/respond_to_terms_service.rb' + - 'app/services/users/set_status_service.rb' + - 'app/services/users/upsert_credit_card_validation_service.rb' + - 'app/services/x509_certificate_revoke_service.rb' + - 'app/uploaders/file_mover.rb' + - 'app/uploaders/object_storage.rb' + - 'app/workers/analytics/usage_trends/counter_job_worker.rb' + - 'app/workers/concerns/dependency_proxy/expireable.rb' + - 'app/workers/concerns/packages/cleanup_artifact_worker.rb' + - 'app/workers/container_expiration_policy_worker.rb' + - 'app/workers/namespaceless_project_destroy_worker.rb' + - 'app/workers/packages/helm/extraction_worker.rb' + - 'app/workers/packages/nuget/extraction_worker.rb' + - 'app/workers/packages/rubygems/extraction_worker.rb' + - 'app/workers/personal_access_tokens/expired_notification_worker.rb' + - 'app/workers/personal_access_tokens/expiring_worker.rb' + - 'app/workers/pipeline_metrics_worker.rb' + - 'app/workers/process_commit_worker.rb' + - 'app/workers/repository_check/clear_worker.rb' + - 'app/workers/repository_check/single_repository_worker.rb' + - 'app/workers/stuck_merge_jobs_worker.rb' + - 'app/workers/x509_issuer_crl_check_worker.rb' + - 'db/migrate/20210428151144_update_invalid_web_hooks.rb' + - 'db/migrate/20210629031900_associate_existing_dast_builds_with_variables.rb' + - 'db/migrate/20210630224625_generate_customers_dot_jwt_signing_key.rb' + - 'db/migrate/20210729123101_confirm_security_bot.rb' + - 'db/migrate/20220413054910_backfill_delayed_group_deletion.rb' + - 'db/post_migrate/20210303121224_update_gitlab_subscriptions_start_at_post_eoa.rb' + - 'db/post_migrate/20210303165302_cleanup_cluster_tokens_with_null_name.rb' + - 'db/post_migrate/20210406144743_backfill_total_tuple_count_for_batched_migrations.rb' + - 'db/post_migrate/20210513155546_backfill_nuget_temporary_packages_to_processing_status.rb' + - 'db/post_migrate/20210601073400_fix_total_stage_in_vsa.rb' + - 'db/post_migrate/20210615234935_fix_batched_migrations_old_format_job_arguments.rb' + - 'db/post_migrate/20210722042939_update_issuable_slas_where_issue_closed.rb' + - 'db/post_migrate/20210731132939_backfill_stage_event_hash.rb' + - 'db/post_migrate/20210809123658_orphaned_invite_tokens_cleanup.rb' + - 'db/post_migrate/20210811122206_update_external_project_bots.rb' + - 'db/post_migrate/20210825150212_cleanup_remaining_orphan_invites.rb' + - 'db/post_migrate/20210826171758_initialize_throttle_unauthenticated_api_columns.rb' + - 'db/post_migrate/20210901153324_slice_merge_request_diff_commit_migrations.rb' + - 'db/post_migrate/20210908132335_disable_job_token_scope_when_unused.rb' + - 'db/post_migrate/20210914095310_cleanup_orphan_project_access_tokens.rb' + - 'db/post_migrate/20211217174331_mark_recalculate_finding_signatures_as_completed.rb' + - 'db/post_migrate/20211220123956_update_invalid_member_states.rb' + - 'db/post_migrate/20220305223212_add_security_training_providers.rb' + - 'db/post_migrate/20220307203459_rename_user_email_lookup_limit_setting_to_search_settings_cleanup.rb' + - 'db/post_migrate/20220322132242_update_pages_onboarding_state.rb' + - 'ee/app/controllers/ee/clusters/clusters_controller.rb' + - 'ee/app/models/approval_merge_request_rule.rb' + - 'ee/app/models/ci/minutes/namespace_monthly_usage.rb' + - 'ee/app/models/ci/minutes/project_monthly_usage.rb' + - 'ee/app/models/concerns/deprecated_approvals_before_merge.rb' + - 'ee/app/models/concerns/epic_tree_sorting.rb' + - 'ee/app/models/concerns/geo/replicable_registry.rb' + - 'ee/app/models/concerns/geo/verification_state.rb' + - 'ee/app/models/ee/description_version.rb' + - 'ee/app/models/ee/environment.rb' + - 'ee/app/models/ee/epic.rb' + - 'ee/app/models/ee/event.rb' + - 'ee/app/models/ee/group.rb' + - 'ee/app/models/ee/iteration.rb' + - 'ee/app/models/ee/namespace_setting.rb' + - 'ee/app/models/ee/project_wiki.rb' + - 'ee/app/models/geo/container_repository_registry.rb' + - 'ee/app/models/geo/design_registry.rb' + - 'ee/app/models/geo/project_registry.rb' + - 'ee/app/models/geo_node.rb' + - 'ee/app/models/incident_management/oncall_rotation.rb' + - 'ee/app/models/vulnerabilities/feedback.rb' + - 'ee/app/services/app_sec/dast/profiles/create_associations_service.rb' + - 'ee/app/services/ci/minutes/additional_packs/change_namespace_service.rb' + - 'ee/app/services/ci/minutes/batch_reset_service.rb' + - 'ee/app/services/ci/minutes/refresh_cached_data_service.rb' + - 'ee/app/services/ci/minutes/reset_usage_service.rb' + - 'ee/app/services/ci/minutes/update_project_and_namespace_usage_service.rb' + - 'ee/app/services/ci/sync_reports_to_approval_rules_service.rb' + - 'ee/app/services/ee/issues/move_service.rb' + - 'ee/app/services/ee/labels/promote_service.rb' + - 'ee/app/services/ee/milestones/promote_service.rb' + - 'ee/app/services/ee/projects/transfer_service.rb' + - 'ee/app/services/ee/users/migrate_to_ghost_user_service.rb' + - 'ee/app/services/epics/strategies/due_date_inherited_strategy.rb' + - 'ee/app/services/epics/strategies/start_date_inherited_strategy.rb' + - 'ee/app/services/geo/job_artifact_deleted_event_store.rb' + - 'ee/app/services/geo/repository_verification_reset.rb' + - 'ee/app/services/incident_management/oncall_rotations/edit_service.rb' + - 'ee/app/services/incident_management/oncall_rotations/remove_participant_service.rb' + - 'ee/app/services/iterations/cadences/create_iterations_in_advance_service.rb' + - 'ee/app/services/iterations/cadences/destroy_service.rb' + - 'ee/app/services/iterations/delete_service.rb' + - 'ee/app/services/iterations/roll_over_issues_service.rb' + - 'ee/app/services/ldap_group_reset_service.rb' + - 'ee/app/services/personal_access_tokens/revoke_invalid_tokens.rb' + - 'ee/app/services/security/findings/cleanup_service.rb' + - 'ee/app/services/security/ingestion/mark_as_resolved_service.rb' + - 'ee/app/services/security/store_findings_metadata_service.rb' + - 'ee/app/services/security/store_scan_service.rb' + - 'ee/app/services/security/update_training_service.rb' + - 'ee/app/services/vulnerabilities/starboard_vulnerability_resolve_service.rb' + - 'ee/app/workers/import_software_licenses_worker.rb' + - 'ee/app/workers/iterations_update_status_worker.rb' + - 'ee/app/workers/sync_seat_link_request_worker.rb' + - 'ee/lib/api/geo_replication.rb' + - 'ee/lib/ee/api/protected_branches.rb' + - 'ee/lib/ee/gitlab/auth/ldap/sync/group.rb' + - 'ee/lib/ee/gitlab/background_migration/backfill_iteration_cadence_id_for_boards.rb' + - 'ee/lib/ee/gitlab/background_migration/migrate_job_artifact_registry_to_ssf.rb' + - 'ee/lib/ee/gitlab/background_migration/migrate_requirements_to_work_items.rb' + - 'ee/lib/ee/gitlab/background_migration/populate_resolved_on_default_branch_column.rb' + - 'ee/lib/ee/gitlab/background_migration/populate_uuids_for_security_findings.rb' + - 'ee/lib/gitlab/geo/replicator.rb' + - 'ee/lib/tasks/migrate/ldap.rake' + - 'ee/spec/controllers/admin/geo/projects_controller_spec.rb' + - 'ee/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb' + - 'ee/spec/controllers/groups/ldaps_controller_spec.rb' + - 'ee/spec/controllers/projects/merge_requests_controller_spec.rb' + - 'ee/spec/controllers/trials_controller_spec.rb' + - 'ee/spec/factories/import_states.rb' + - 'ee/spec/features/admin/admin_settings_spec.rb' + - 'ee/spec/features/epic_boards/epic_boards_sidebar_spec.rb' + - 'ee/spec/features/projects/settings/ee/service_desk_setting_spec.rb' + - 'ee/spec/features/projects/settings/issues_settings_spec.rb' + - 'ee/spec/features/projects/settings/protected_environments_spec.rb' + - 'ee/spec/features/projects/user_applies_custom_file_template_spec.rb' + - 'ee/spec/features/trials/select_namespace_spec.rb' + - 'ee/spec/finders/geo/repository_verification_finder_spec.rb' + - 'ee/spec/finders/security/findings_finder_spec.rb' + - 'ee/spec/finders/security/training_urls_finder_spec.rb' + - 'ee/spec/finders/template_finder_spec.rb' + - 'ee/spec/graphql/mutations/issues/set_epic_spec.rb' + - 'ee/spec/graphql/mutations/issues/set_escalation_policy_spec.rb' + - 'ee/spec/graphql/mutations/issues/set_iteration_spec.rb' + - 'ee/spec/graphql/resolvers/boards/epic_lists_resolvers_spec.rb' + - 'ee/spec/helpers/ee/blob_helper_spec.rb' + - 'ee/spec/helpers/push_rules_helper_spec.rb' + - 'ee/spec/lib/banzai/filter/references/epic_reference_filter_spec.rb' + - 'ee/spec/lib/banzai/filter/references/iteration_reference_filter_spec.rb' + - 'ee/spec/lib/banzai/filter/references/vulnerability_reference_filters_spec.rb' + - 'ee/spec/lib/ee/api/helpers_spec.rb' + - 'ee/spec/lib/ee/audit/group_changes_auditor_spec.rb' + - 'ee/spec/lib/ee/audit/project_changes_auditor_spec.rb' + - 'ee/spec/lib/ee/audit/project_ci_cd_setting_changes_auditor_spec.rb' + - 'ee/spec/lib/ee/audit/project_feature_changes_auditor_spec.rb' + - 'ee/spec/lib/ee/audit/protected_branches_changes_auditor_spec.rb' + - 'ee/spec/lib/ee/gitlab/auth/ldap/sync/group_spec.rb' + - 'ee/spec/lib/ee/gitlab/background_migration/fix_incorrect_max_seats_used_spec.rb' + - 'ee/spec/lib/ee/gitlab/checks/push_rules/commit_check_spec.rb' + - 'ee/spec/lib/gitlab/auth/ldap/access_spec.rb' + - 'ee/spec/lib/gitlab/auth/saml/user_spec.rb' + - 'ee/spec/lib/gitlab/custom_file_templates_spec.rb' + - 'ee/spec/lib/gitlab/geo/geo_tasks_spec.rb' + - 'ee/spec/lib/gitlab/geo/jwt_request_decoder_spec.rb' + - 'ee/spec/lib/gitlab/geo/replication/file_downloader_spec.rb' + - 'ee/spec/lib/gitlab/geo/replication/file_transfer_spec.rb' + - 'ee/spec/lib/gitlab/geo/replication/job_artifact_downloader_spec.rb' + - 'ee/spec/lib/gitlab/geo/signed_data_spec.rb' + - 'ee/spec/lib/gitlab/git_access_spec.rb' + - 'ee/spec/models/application_setting_spec.rb' + - 'ee/spec/models/ci/minutes/namespace_monthly_usage_spec.rb' + - 'ee/spec/models/concerns/elastic/note_spec.rb' + - 'ee/spec/models/concerns/geo/verification_state_spec.rb' + - 'ee/spec/models/dast/profile_schedule_spec.rb' + - 'ee/spec/models/ee/group_spec.rb' + - 'ee/spec/models/ee/groups/feature_setting_spec.rb' + - 'ee/spec/models/ee/iteration_spec.rb' + - 'ee/spec/models/ee/iterations/cadence_spec.rb' + - 'ee/spec/models/ee/key_spec.rb' + - 'ee/spec/models/ee/namespace_limit_spec.rb' + - 'ee/spec/models/ee/vulnerability_spec.rb' + - 'ee/spec/models/geo_node_spec.rb' + - 'ee/spec/models/geo_node_status_spec.rb' + - 'ee/spec/models/group_wiki_repository_spec.rb' + - 'ee/spec/models/instance_security_dashboard_spec.rb' + - 'ee/spec/models/merge_request/blocking_spec.rb' + - 'ee/spec/models/merge_train_spec.rb' + - 'ee/spec/models/packages/package_file_spec.rb' + - 'ee/spec/models/project_feature_spec.rb' + - 'ee/spec/models/project_import_state_spec.rb' + - 'ee/spec/models/project_spec.rb' + - 'ee/spec/models/project_team_spec.rb' + - 'ee/spec/models/push_rule_spec.rb' + - 'ee/spec/models/requirements_management/requirement_spec.rb' + - 'ee/spec/models/security/scan_spec.rb' + - 'ee/spec/models/snippet_repository_spec.rb' + - 'ee/spec/models/vulnerabilities/feedback_spec.rb' + - 'ee/spec/models/vulnerabilities/stat_diff_spec.rb' + - 'ee/spec/policies/group_policy_spec.rb' + - 'ee/spec/policies/project_policy_spec.rb' + - 'ee/spec/requests/api/epic_issues_spec.rb' + - 'ee/spec/requests/api/graphql/mutations/issues/promote_to_epic_spec.rb' + - 'ee/spec/requests/api/graphql/mutations/issues/set_epic_spec.rb' + - 'ee/spec/requests/api/groups_spec.rb' + - 'ee/spec/requests/api/internal/app_sec/dast/site_validations_spec.rb' + - 'ee/spec/requests/api/internal/kubernetes_spec.rb' + - 'ee/spec/requests/api/namespaces_spec.rb' + - 'ee/spec/requests/api/project_approvals_spec.rb' + - 'ee/spec/requests/git_http_geo_spec.rb' + - 'ee/spec/requests/projects/merge_requests_controller_spec.rb' + - 'ee/spec/serializers/merge_request_widget_entity_spec.rb' + - 'ee/spec/services/ci/create_pipeline_service_spec.rb' + - 'ee/spec/services/ci/minutes/email_notification_service_spec.rb' + - 'ee/spec/services/ci/register_job_service_spec.rb' + - 'ee/spec/services/ci_cd/setup_project_spec.rb' + - 'ee/spec/services/ee/boards/issues/list_service_spec.rb' + - 'ee/spec/services/ee/notification_service_spec.rb' + - 'ee/spec/services/ee/releases/create_evidence_service_spec.rb' + - 'ee/spec/services/epic_issues/update_service_spec.rb' + - 'ee/spec/services/epics/issue_promote_service_spec.rb' + - 'ee/spec/services/epics/update_service_spec.rb' + - 'ee/spec/services/geo/file_download_service_spec.rb' + - 'ee/spec/services/geo/file_registry_removal_service_spec.rb' + - 'ee/spec/services/geo/hashed_storage_migration_service_spec.rb' + - 'ee/spec/services/groups/create_service_spec.rb' + - 'ee/spec/services/groups/update_service_spec.rb' + - 'ee/spec/services/merge_trains/check_status_service_spec.rb' + - 'ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb' + - 'ee/spec/services/merge_trains/refresh_service_spec.rb' + - 'ee/spec/services/projects/setup_ci_cd_spec.rb' + - 'ee/spec/services/projects/update_mirror_service_spec.rb' + - 'ee/spec/services/security/ingestion/ingest_report_slice_service_spec.rb' + - 'ee/spec/services/security/security_orchestration_policies/create_pipeline_service_spec.rb' + - 'ee/spec/services/vulnerabilities/starboard_vulnerability_resolve_service_spec.rb' + - 'ee/spec/services/vulnerabilities/statistics/adjustment_service_spec.rb' + - 'ee/spec/services/vulnerabilities/statistics/update_service_spec.rb' + - 'ee/spec/support/helpers/ee/geo_helpers.rb' + - 'ee/spec/support/shared_examples/models/requirement_issues_examples.rb' + - 'ee/spec/support/shared_examples/policies/protected_environments_shared_examples.rb' + - 'ee/spec/workers/app_sec/dast/profile_schedule_worker_spec.rb' + - 'ee/spec/workers/ee/repository_check/batch_worker_spec.rb' + - 'ee/spec/workers/geo/repositories_clean_up_worker_spec.rb' + - 'ee/spec/workers/geo/repository_shard_sync_worker_spec.rb' + - 'ee/spec/workers/geo/repository_sync_worker_spec.rb' + - 'ee/spec/workers/geo/repository_verification/primary/batch_worker_spec.rb' + - 'ee/spec/workers/geo/repository_verification/primary/shard_worker_spec.rb' + - 'ee/spec/workers/geo/repository_verification/secondary/scheduler_worker_spec.rb' + - 'ee/spec/workers/geo/repository_verification/secondary/single_worker_spec.rb' + - 'ee/spec/workers/geo/verification_state_backfill_service_spec.rb' + - 'ee/spec/workers/import_software_licenses_worker_spec.rb' + - 'ee/spec/workers/iterations/roll_over_issues_worker_spec.rb' + - 'ee/spec/workers/iterations_update_status_worker_spec.rb' + - 'ee/spec/workers/network_policy_metrics_worker_spec.rb' + - 'ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb' + - 'ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb' + - 'ee/spec/workers/update_all_mirrors_worker_spec.rb' + - 'lib/api/commit_statuses.rb' + - 'lib/api/usage_data.rb' + - 'lib/gitlab/background_migration/add_primary_email_to_emails_if_user_confirmed.rb' + - 'lib/gitlab/background_migration/backfill_artifact_expiry_date.rb' + - 'lib/gitlab/background_migration/backfill_ci_queuing_tables.rb' + - 'lib/gitlab/background_migration/backfill_draft_status_on_merge_requests.rb' + - 'lib/gitlab/background_migration/backfill_jira_tracker_deployment_type2.rb' + - 'lib/gitlab/background_migration/backfill_member_namespace_for_group_members.rb' + - 'lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route.rb' + - 'lib/gitlab/background_migration/backfill_namespace_traversal_ids_roots.rb' + - 'lib/gitlab/background_migration/backfill_projects_with_coverage.rb' + - 'lib/gitlab/background_migration/backfill_topics_title.rb' + - 'lib/gitlab/background_migration/backfill_user_namespace.rb' + - 'lib/gitlab/background_migration/backfill_work_item_type_id_for_issues.rb' + - 'lib/gitlab/background_migration/cleanup_draft_data_from_faulty_regex.rb' + - 'lib/gitlab/background_migration/copy_column_using_background_migration_job.rb' + - 'lib/gitlab/background_migration/legacy_upload_mover.rb' + - 'lib/gitlab/background_migration/merge_topics_with_same_name.rb' + - 'lib/gitlab/background_migration/migrate_merge_request_diff_commit_users.rb' + - 'lib/gitlab/background_migration/migrate_null_private_profile_to_false.rb' + - 'lib/gitlab/background_migration/migrate_personal_namespace_project_maintainer_to_owner.rb' + - 'lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics.rb' + - 'lib/gitlab/background_migration/migrate_shimo_confluence_integration_category.rb' + - 'lib/gitlab/background_migration/migrate_u2f_webauthn.rb' + - 'lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds.rb' + - 'lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb' + - 'lib/gitlab/background_migration/remove_all_trace_expiration_dates.rb' + - 'lib/gitlab/background_migration/reset_duplicate_ci_runners_token_encrypted_values_on_projects.rb' + - 'lib/gitlab/background_migration/reset_duplicate_ci_runners_token_values_on_projects.rb' + - 'lib/gitlab/bitbucket_import/importer.rb' + - 'lib/gitlab/bitbucket_server_import/importer.rb' + - 'lib/gitlab/ci/tags/bulk_insert.rb' + - 'lib/gitlab/ci/trace.rb' + - 'lib/gitlab/composer/cache.rb' + - 'lib/gitlab/database/background_migration_job.rb' + - 'lib/gitlab/database/postgresql_adapter/dump_schema_versions_mixin.rb' + - 'lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb' + - 'lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb' + - 'lib/gitlab/database/schema_migrations.rb' + - 'lib/gitlab/etag_caching/middleware.rb' + - 'lib/gitlab/fogbugz_import/importer.rb' + - 'lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb' + - 'lib/gitlab/github_import/importer/pull_request_review_importer.rb' + - 'lib/gitlab/import/set_async_jid.rb' + - 'lib/gitlab/jira_import/handle_labels_service.rb' + - 'lib/gitlab/job_waiter.rb' + - 'lib/gitlab/legacy_github_import/importer.rb' + - 'lib/gitlab/markdown_cache/active_record/extension.rb' + - 'lib/gitlab/otp_key_rotator.rb' + - 'lib/gitlab/seeder.rb' + - 'lib/tasks/ci/cleanup.rake' + - 'lib/tasks/gitlab/external_diffs.rake' + - 'lib/tasks/gitlab/ldap.rake' + - 'lib/tasks/gitlab/user_management.rake' + - 'lib/tasks/migrate/migrate_iids.rake' + - 'spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb' + - 'spec/controllers/groups_controller_spec.rb' + - 'spec/controllers/import/bitbucket_controller_spec.rb' + - 'spec/controllers/import/gitlab_controller_spec.rb' + - 'spec/controllers/omniauth_callbacks_controller_spec.rb' + - 'spec/controllers/projects/forks_controller_spec.rb' + - 'spec/controllers/projects/graphs_controller_spec.rb' + - 'spec/controllers/projects/jobs_controller_spec.rb' + - 'spec/controllers/projects/merge_requests/content_controller_spec.rb' + - 'spec/controllers/projects/merge_requests_controller_spec.rb' + - 'spec/controllers/projects/notes_controller_spec.rb' + - 'spec/controllers/projects/pipelines/tests_controller_spec.rb' + - 'spec/controllers/projects/repositories_controller_spec.rb' + - 'spec/controllers/projects/settings/ci_cd_controller_spec.rb' + - 'spec/controllers/projects/starrers_controller_spec.rb' + - 'spec/controllers/projects_controller_spec.rb' + - 'spec/controllers/uploads_controller_spec.rb' + - 'spec/factories/alert_management/alerts.rb' + - 'spec/factories/container_expiration_policies.rb' + - 'spec/factories/design_management/versions.rb' + - 'spec/factories/environments.rb' + - 'spec/factories/import_states.rb' + - 'spec/factories/projects.rb' + - 'spec/factories/usage_data.rb' + - 'spec/features/admin/admin_settings_spec.rb' + - 'spec/features/admin/admin_uses_repository_checks_spec.rb' + - 'spec/features/dashboard/projects_spec.rb' + - 'spec/features/groups_spec.rb' + - 'spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb' + - 'spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb' + - 'spec/features/issues/discussion_lock_spec.rb' + - 'spec/features/merge_request/merge_request_discussion_lock_spec.rb' + - 'spec/features/merge_request/user_creates_image_diff_notes_spec.rb' + - 'spec/features/merge_request/user_locks_discussion_spec.rb' + - 'spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb' + - 'spec/features/merge_request/user_sees_diff_spec.rb' + - 'spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb' + - 'spec/features/merge_request/user_sees_merge_widget_spec.rb' + - 'spec/features/merge_request/user_sees_pipelines_spec.rb' + - 'spec/features/merge_request/user_views_merge_request_from_deleted_fork_spec.rb' + - 'spec/features/monitor_sidebar_link_spec.rb' + - 'spec/features/password_reset_spec.rb' + - 'spec/features/profiles/emails_spec.rb' + - 'spec/features/projects/blobs/blob_show_spec.rb' + - 'spec/features/projects/diffs/diff_show_spec.rb' + - 'spec/features/projects/features_visibility_spec.rb' + - 'spec/features/projects/fork_spec.rb' + - 'spec/features/projects/jobs/user_browses_jobs_spec.rb' + - 'spec/features/projects/jobs_spec.rb' + - 'spec/features/projects/members/invite_group_spec.rb' + - 'spec/features/projects/milestones/milestone_spec.rb' + - 'spec/features/projects/pipeline_schedules_spec.rb' + - 'spec/features/projects/pipelines/pipeline_spec.rb' + - 'spec/features/projects/settings/service_desk_setting_spec.rb' + - 'spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb' + - 'spec/features/projects/user_sees_sidebar_spec.rb' + - 'spec/features/projects_spec.rb' + - 'spec/features/refactor_blob_viewer_disabled/projects/blobs/blob_show_spec.rb' + - 'spec/features/u2f_spec.rb' + - 'spec/features/users/show_spec.rb' + - 'spec/features/webauthn_spec.rb' + - 'spec/finders/groups_finder_spec.rb' + - 'spec/finders/notes_finder_spec.rb' + - 'spec/finders/packages/go/package_finder_spec.rb' + - 'spec/finders/packages/maven/package_finder_spec.rb' + - 'spec/finders/packages/npm/package_finder_spec.rb' + - 'spec/finders/packages/nuget/package_finder_spec.rb' + - 'spec/finders/packages/package_finder_spec.rb' + - 'spec/finders/projects_finder_spec.rb' + - 'spec/finders/releases/group_releases_finder_spec.rb' + - 'spec/finders/releases_finder_spec.rb' + - 'spec/finders/user_group_notification_settings_finder_spec.rb' + - 'spec/graphql/mutations/custom_emoji/destroy_spec.rb' + - 'spec/graphql/mutations/issues/set_escalation_status_spec.rb' + - 'spec/graphql/mutations/issues/update_spec.rb' + - 'spec/graphql/resolvers/ci/test_suite_resolver_spec.rb' + - 'spec/graphql/types/project_type_spec.rb' + - 'spec/helpers/auth_helper_spec.rb' + - 'spec/helpers/events_helper_spec.rb' + - 'spec/helpers/groups_helper_spec.rb' + - 'spec/helpers/import_helper_spec.rb' + - 'spec/helpers/members_helper_spec.rb' + - 'spec/helpers/projects_helper_spec.rb' + - 'spec/initializers/active_record_locking_spec.rb' + - 'spec/lib/api/helpers_spec.rb' + - 'spec/lib/backup/repositories_spec.rb' + - 'spec/lib/banzai/filter/references/issue_reference_filter_spec.rb' + - 'spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb' + - 'spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb' + - 'spec/lib/banzai/reference_parser/merge_request_parser_spec.rb' + - 'spec/lib/banzai/reference_parser/snippet_parser_spec.rb' + - 'spec/lib/gitlab/asciidoc_spec.rb' + - 'spec/lib/gitlab/auth/saml/user_spec.rb' + - 'spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb' + - 'spec/lib/gitlab/background_migration/batched_migration_job_spec.rb' + - 'spec/lib/gitlab/background_migration/update_timelogs_null_spent_at_spec.rb' + - 'spec/lib/gitlab/ci/variables/builder/group_spec.rb' + - 'spec/lib/gitlab/ci/variables/builder/project_spec.rb' + - 'spec/lib/gitlab/contributions_calendar_spec.rb' + - 'spec/lib/gitlab/cycle_analytics/permissions_spec.rb' + - 'spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb' + - 'spec/lib/gitlab/database/batch_count_spec.rb' + - 'spec/lib/gitlab/database/consistency_checker_spec.rb' + - 'spec/lib/gitlab/database/load_balancing/connection_proxy_spec.rb' + - 'spec/lib/gitlab/database/load_balancing_spec.rb' + - 'spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb' + - 'spec/lib/gitlab/database/query_analyzers/prevent_cross_database_modification_spec.rb' + - 'spec/lib/gitlab/database/schema_migrations/migrations_spec.rb' + - 'spec/lib/gitlab/discussions_diff/file_collection_spec.rb' + - 'spec/lib/gitlab/email/handler/create_note_handler_spec.rb' + - 'spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb' + - 'spec/lib/gitlab/etag_caching/store_spec.rb' + - 'spec/lib/gitlab/git_access_spec.rb' + - 'spec/lib/gitlab/git_access_wiki_spec.rb' + - 'spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb' + - 'spec/lib/gitlab/middleware/go_spec.rb' + - 'spec/lib/gitlab/middleware/query_analyzer_spec.rb' + - 'spec/lib/gitlab/object_hierarchy_spec.rb' + - 'spec/lib/gitlab/pages_transfer_spec.rb' + - 'spec/lib/gitlab/sidekiq_middleware/query_analyzer_spec.rb' + - 'spec/lib/sidebars/projects/menus/project_information_menu_spec.rb' + - 'spec/mailers/notify_spec.rb' + - 'spec/migrations/20211126115449_encrypt_static_objects_external_storage_auth_token_spec.rb' + - 'spec/migrations/remove_duplicate_dast_site_tokens_spec.rb' + - 'spec/migrations/schedule_update_timelogs_null_spent_at_spec.rb' + - 'spec/models/application_setting_spec.rb' + - 'spec/models/ci/build_dependencies_spec.rb' + - 'spec/models/ci/build_spec.rb' + - 'spec/models/ci/group_spec.rb' + - 'spec/models/ci/legacy_stage_spec.rb' + - 'spec/models/ci/pipeline_schedule_spec.rb' + - 'spec/models/ci/pipeline_spec.rb' + - 'spec/models/ci/processable_spec.rb' + - 'spec/models/ci/resource_group_spec.rb' + - 'spec/models/ci/runner_spec.rb' + - 'spec/models/ci/stage_spec.rb' + - 'spec/models/commit_signatures/gpg_signature_spec.rb' + - 'spec/models/commit_status_spec.rb' + - 'spec/models/concerns/cache_markdown_field_spec.rb' + - 'spec/models/concerns/deployment_platform_spec.rb' + - 'spec/models/concerns/deprecated_assignee_spec.rb' + - 'spec/models/concerns/each_batch_spec.rb' + - 'spec/models/concerns/pg_full_text_searchable_spec.rb' + - 'spec/models/concerns/project_features_compatibility_spec.rb' + - 'spec/models/concerns/spammable_spec.rb' + - 'spec/models/container_repository_spec.rb' + - 'spec/models/deploy_keys_project_spec.rb' + - 'spec/models/deploy_token_spec.rb' + - 'spec/models/diff_discussion_spec.rb' + - 'spec/models/diff_note_spec.rb' + - 'spec/models/environment_spec.rb' + - 'spec/models/group_spec.rb' + - 'spec/models/guest_spec.rb' + - 'spec/models/integration_spec.rb' + - 'spec/models/issue_spec.rb' + - 'spec/models/loose_foreign_keys/deleted_record_spec.rb' + - 'spec/models/member_spec.rb' + - 'spec/models/members/group_member_spec.rb' + - 'spec/models/members/project_member_spec.rb' + - 'spec/models/merge_request_diff_spec.rb' + - 'spec/models/merge_request_spec.rb' + - 'spec/models/namespace/traversal_hierarchy_spec.rb' + - 'spec/models/namespace_spec.rb' + - 'spec/models/note_spec.rb' + - 'spec/models/project_authorization_spec.rb' + - 'spec/models/project_feature_spec.rb' + - 'spec/models/project_spec.rb' + - 'spec/models/project_statistics_spec.rb' + - 'spec/models/projects/build_artifacts_size_refresh_spec.rb' + - 'spec/models/projects/topic_spec.rb' + - 'spec/models/remote_mirror_spec.rb' + - 'spec/models/repository_spec.rb' + - 'spec/models/route_spec.rb' + - 'spec/models/snippet_repository_spec.rb' + - 'spec/models/user_spec.rb' + - 'spec/policies/ci/build_policy_spec.rb' + - 'spec/policies/custom_emoji_policy_spec.rb' + - 'spec/policies/note_policy_spec.rb' + - 'spec/policies/project_policy_spec.rb' + - 'spec/presenters/ci/build_presenter_spec.rb' + - 'spec/presenters/project_presenter_spec.rb' + - 'spec/requests/api/ci/job_artifacts_spec.rb' + - 'spec/requests/api/ci/runner/jobs_request_post_spec.rb' + - 'spec/requests/api/container_repositories_spec.rb' + - 'spec/requests/api/graphql/container_repository/container_repository_details_spec.rb' + - 'spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb' + - 'spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb' + - 'spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb' + - 'spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb' + - 'spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb' + - 'spec/requests/api/graphql/mutations/snippets/create_spec.rb' + - 'spec/requests/api/graphql/mutations/snippets/destroy_spec.rb' + - 'spec/requests/api/graphql/mutations/snippets/update_spec.rb' + - 'spec/requests/api/helm_packages_spec.rb' + - 'spec/requests/api/issues/get_group_issues_spec.rb' + - 'spec/requests/api/issues/get_project_issues_spec.rb' + - 'spec/requests/api/issues/issues_spec.rb' + - 'spec/requests/api/merge_requests_spec.rb' + - 'spec/requests/api/notes_spec.rb' + - 'spec/requests/api/nuget_group_packages_spec.rb' + - 'spec/requests/api/projects_spec.rb' + - 'spec/requests/api/pypi_packages_spec.rb' + - 'spec/requests/api/releases_spec.rb' + - 'spec/requests/api/rubygem_packages_spec.rb' + - 'spec/requests/api/snippets_spec.rb' + - 'spec/requests/api/tags_spec.rb' + - 'spec/requests/git_http_spec.rb' + - 'spec/requests/groups/settings/access_tokens_controller_spec.rb' + - 'spec/requests/jwt_controller_spec.rb' + - 'spec/requests/lfs_http_spec.rb' + - 'spec/requests/projects/merge_requests_spec.rb' + - 'spec/requests/projects/settings/access_tokens_controller_spec.rb' + - 'spec/services/alert_management/create_alert_issue_service_spec.rb' + - 'spec/services/ci/compare_reports_base_service_spec.rb' + - 'spec/services/ci/compare_test_reports_service_spec.rb' + - 'spec/services/ci/job_artifacts/update_unknown_locked_status_service_spec.rb' + - 'spec/services/ci/register_job_service_spec.rb' + - 'spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb' + - 'spec/services/ci/retry_job_service_spec.rb' + - 'spec/services/ci/retry_pipeline_service_spec.rb' + - 'spec/services/ci/test_failure_history_service_spec.rb' + - 'spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb' + - 'spec/services/container_expiration_policies/cleanup_service_spec.rb' + - 'spec/services/dependency_proxy/find_cached_manifest_service_spec.rb' + - 'spec/services/deployments/update_environment_service_spec.rb' + - 'spec/services/groups/create_service_spec.rb' + - 'spec/services/groups/transfer_service_spec.rb' + - 'spec/services/groups/update_service_spec.rb' + - 'spec/services/incident_management/pager_duty/process_webhook_service_spec.rb' + - 'spec/services/issuable/common_system_notes_service_spec.rb' + - 'spec/services/issues/clone_service_spec.rb' + - 'spec/services/issues/close_service_spec.rb' + - 'spec/services/issues/update_service_spec.rb' + - 'spec/services/members/destroy_service_spec.rb' + - 'spec/services/merge_requests/get_urls_service_spec.rb' + - 'spec/services/merge_requests/merge_service_spec.rb' + - 'spec/services/merge_requests/refresh_service_spec.rb' + - 'spec/services/merge_requests/reopen_service_spec.rb' + - 'spec/services/merge_requests/update_service_spec.rb' + - 'spec/services/notes/update_service_spec.rb' + - 'spec/services/notification_service_spec.rb' + - 'spec/services/packages/maven/metadata/sync_service_spec.rb' + - 'spec/services/packages/nuget/search_service_spec.rb' + - 'spec/services/projects/container_repository/delete_tags_service_spec.rb' + - 'spec/services/projects/create_service_spec.rb' + - 'spec/services/projects/destroy_service_spec.rb' + - 'spec/services/projects/fork_service_spec.rb' + - 'spec/services/projects/refresh_build_artifacts_size_statistics_service_spec.rb' + - 'spec/services/repositories/destroy_service_spec.rb' + - 'spec/services/spam/ham_service_spec.rb' + - 'spec/services/system_notes/design_management_service_spec.rb' + - 'spec/services/system_notes/issuables_service_spec.rb' + - 'spec/services/system_notes/time_tracking_service_spec.rb' + - 'spec/services/users/repair_ldap_blocked_service_spec.rb' + - 'spec/services/work_items/task_list_reference_replacement_service_spec.rb' + - 'spec/support/helpers/access_matchers_helpers.rb' + - 'spec/support/matchers/access_matchers_for_controller.rb' + - 'spec/support/shared_contexts/email_shared_context.rb' + - 'spec/support/shared_contexts/finders/packages/npm/package_finder_shared_context.rb' + - 'spec/support/shared_contexts/mailers/notify_shared_context.rb' + - 'spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb' + - 'spec/support/shared_examples/ci/stuck_builds_shared_examples.rb' + - 'spec/support/shared_examples/controllers/create_notes_rate_limit_shared_examples.rb' + - 'spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb' + - 'spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb' + - 'spec/support/shared_examples/features/2fa_shared_examples.rb' + - 'spec/support/shared_examples/features/access_tokens_shared_examples.rb' + - 'spec/support/shared_examples/features/sidebar_shared_examples.rb' + - 'spec/support/shared_examples/lib/banzai/reference_parser_shared_examples.rb' + - 'spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb' + - 'spec/support/shared_examples/models/concerns/featurable_shared_examples.rb' + - 'spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb' + - 'spec/support/shared_examples/models/members_notifications_shared_example.rb' + - 'spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb' + - 'spec/support/shared_examples/models/throttled_touch_shared_examples.rb' + - 'spec/support/shared_examples/policies/resource_access_token_shared_examples.rb' + - 'spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb' + - 'spec/support/shared_examples/requests/api/members_shared_examples.rb' + - 'spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb' + - 'spec/support/shared_examples/requests/user_activity_shared_examples.rb' + - 'spec/support/shared_examples/services/boards/lists_list_service_shared_examples.rb' + - 'spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb' + - 'spec/support/shared_examples/services/notification_service_shared_examples.rb' + - 'spec/support/shared_examples/views/pipeline_status_changes_email.rb' + - 'spec/support/trace/trace_helpers.rb' + - 'spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb' + - 'spec/tasks/gitlab/artifacts/check_rake_spec.rb' + - 'spec/tasks/gitlab/external_diffs_rake_spec.rb' + - 'spec/tasks/gitlab/uploads/check_rake_spec.rb' + - 'spec/uploaders/job_artifact_uploader_spec.rb' + - 'spec/views/groups/edit.html.haml_spec.rb' + - 'spec/views/projects/environments/terminal.html.haml_spec.rb' + - 'spec/views/projects/tracing/show.html.haml_spec.rb' + - 'spec/workers/auto_devops/disable_worker_spec.rb' + - 'spec/workers/build_finished_worker_spec.rb' + - 'spec/workers/ci/merge_requests/add_todo_when_build_fails_worker_spec.rb' + - 'spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb' + - 'spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb' + - 'spec/workers/container_expiration_policy_worker_spec.rb' + - 'spec/workers/container_registry/migration/guard_worker_spec.rb' + - 'spec/workers/gitlab/github_import/advance_stage_worker_spec.rb' + - 'spec/workers/packages/cleanup_package_file_worker_spec.rb' + - 'spec/workers/packages/cleanup_package_registry_worker_spec.rb' + - 'spec/workers/packages/composer/cache_cleanup_worker_spec.rb' + - 'spec/workers/pipeline_schedule_worker_spec.rb' + - 'spec/workers/remote_mirror_notification_worker_spec.rb' + - 'spec/workers/repository_check/batch_worker_spec.rb' + - 'spec/workers/repository_check/clear_worker_spec.rb' diff --git a/.rubocop_todo/rspec/verified_doubles.yml b/.rubocop_todo/rspec/verified_doubles.yml index aed96909fe3..e272fbc555c 100644 --- a/.rubocop_todo/rspec/verified_doubles.yml +++ b/.rubocop_todo/rspec/verified_doubles.yml @@ -332,7 +332,6 @@ RSpec/VerifiedDoubles: - spec/features/projects/integrations/user_activates_jira_spec.rb - spec/finders/ci/auth_job_finder_spec.rb - spec/finders/merge_requests/oldest_per_commit_finder_spec.rb - - spec/finders/projects/serverless/functions_finder_spec.rb - spec/finders/repositories/changelog_commits_finder_spec.rb - spec/finders/repositories/changelog_tag_finder_spec.rb - spec/graphql/features/authorization_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 86e9d18b31d..5f2e4f18ca1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1088,7 +1088,7 @@ GEM rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) - rspec-mocks (3.10.2) + rspec-mocks (3.10.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) rspec-parameterized (0.5.0) diff --git a/app/assets/javascripts/pages/projects/serverless/index.js b/app/assets/javascripts/pages/projects/serverless/index.js deleted file mode 100644 index 9ae81b327b1..00000000000 --- a/app/assets/javascripts/pages/projects/serverless/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import ServerlessBundle from '~/serverless/serverless_bundle'; - -new ServerlessBundle(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/serverless/components/area.vue b/app/assets/javascripts/serverless/components/area.vue deleted file mode 100644 index 0b158ff3e95..00000000000 --- a/app/assets/javascripts/serverless/components/area.vue +++ /dev/null @@ -1,145 +0,0 @@ - - - diff --git a/app/assets/javascripts/serverless/components/empty_state.vue b/app/assets/javascripts/serverless/components/empty_state.vue deleted file mode 100644 index 6d1cea519c4..00000000000 --- a/app/assets/javascripts/serverless/components/empty_state.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - diff --git a/app/assets/javascripts/serverless/components/environment_row.vue b/app/assets/javascripts/serverless/components/environment_row.vue deleted file mode 100644 index 01030172ea8..00000000000 --- a/app/assets/javascripts/serverless/components/environment_row.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - diff --git a/app/assets/javascripts/serverless/components/function_details.vue b/app/assets/javascripts/serverless/components/function_details.vue deleted file mode 100644 index d2306c2d8bd..00000000000 --- a/app/assets/javascripts/serverless/components/function_details.vue +++ /dev/null @@ -1,94 +0,0 @@ - - - diff --git a/app/assets/javascripts/serverless/components/function_row.vue b/app/assets/javascripts/serverless/components/function_row.vue deleted file mode 100644 index fab9c0a75e7..00000000000 --- a/app/assets/javascripts/serverless/components/function_row.vue +++ /dev/null @@ -1,77 +0,0 @@ - - - diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue deleted file mode 100644 index e9461aa3ead..00000000000 --- a/app/assets/javascripts/serverless/components/functions.vue +++ /dev/null @@ -1,139 +0,0 @@ - - - diff --git a/app/assets/javascripts/serverless/components/missing_prometheus.vue b/app/assets/javascripts/serverless/components/missing_prometheus.vue deleted file mode 100644 index d9e6bb5009e..00000000000 --- a/app/assets/javascripts/serverless/components/missing_prometheus.vue +++ /dev/null @@ -1,57 +0,0 @@ - - - diff --git a/app/assets/javascripts/serverless/components/pod_box.vue b/app/assets/javascripts/serverless/components/pod_box.vue deleted file mode 100644 index 04d3641bce3..00000000000 --- a/app/assets/javascripts/serverless/components/pod_box.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/app/assets/javascripts/serverless/components/url.vue b/app/assets/javascripts/serverless/components/url.vue deleted file mode 100644 index b105f49e475..00000000000 --- a/app/assets/javascripts/serverless/components/url.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/app/assets/javascripts/serverless/constants.js b/app/assets/javascripts/serverless/constants.js deleted file mode 100644 index 42c9ee983b4..00000000000 --- a/app/assets/javascripts/serverless/constants.js +++ /dev/null @@ -1,10 +0,0 @@ -export const MAX_REQUESTS = 3; // max number of times to retry - -export const X_INTERVAL = 5; // Reflects the number of verticle bars on the x-axis - -export const CHECKING_INSTALLED = 'checking'; // The backend is still determining whether or not Knative is installed - -export const TIMEOUT = 'timeout'; - -export const DEPRECATION_POST_LINK = - 'https://about.gitlab.com/releases/2021/09/22/gitlab-14-3-released/#gitlab-serverless'; diff --git a/app/assets/javascripts/serverless/event_hub.js b/app/assets/javascripts/serverless/event_hub.js deleted file mode 100644 index e31806ad199..00000000000 --- a/app/assets/javascripts/serverless/event_hub.js +++ /dev/null @@ -1,3 +0,0 @@ -import createEventHub from '~/helpers/event_hub_factory'; - -export default createEventHub(); diff --git a/app/assets/javascripts/serverless/serverless_bundle.js b/app/assets/javascripts/serverless/serverless_bundle.js deleted file mode 100644 index e8d87a40fc7..00000000000 --- a/app/assets/javascripts/serverless/serverless_bundle.js +++ /dev/null @@ -1,67 +0,0 @@ -import Vue from 'vue'; -import FunctionDetails from './components/function_details.vue'; -import Functions from './components/functions.vue'; -import { createStore } from './store'; - -export default class Serverless { - constructor() { - if (document.querySelector('.js-serverless-function-details-page') != null) { - const entryPointData = document.querySelector('.js-serverless-function-details-page').dataset; - const store = createStore(entryPointData); - - const { - serviceName, - serviceDescription, - serviceEnvironment, - serviceUrl, - serviceNamespace, - servicePodcount, - serviceMetricsUrl, - prometheus, - } = entryPointData; - const el = document.querySelector('#js-serverless-function-details'); - - const service = { - name: serviceName, - description: serviceDescription, - environment: serviceEnvironment, - url: serviceUrl, - namespace: serviceNamespace, - podcount: servicePodcount, - metricsUrl: serviceMetricsUrl, - }; - - this.functionDetails = new Vue({ - el, - store, - render(createElement) { - return createElement(FunctionDetails, { - props: { - func: service, - hasPrometheus: prometheus !== undefined, - }, - }); - }, - }); - } else { - const entryPointData = document.querySelector('.js-serverless-functions-page').dataset; - const store = createStore(entryPointData); - - const el = document.querySelector('#js-serverless-functions'); - this.functions = new Vue({ - el, - store, - render(createElement) { - return createElement(Functions); - }, - }); - } - } - - destroy() { - this.destroyed = true; - - this.functions.$destroy(); - this.functionDetails.$destroy(); - } -} diff --git a/app/assets/javascripts/serverless/store/actions.js b/app/assets/javascripts/serverless/store/actions.js deleted file mode 100644 index 166cd796680..00000000000 --- a/app/assets/javascripts/serverless/store/actions.js +++ /dev/null @@ -1,131 +0,0 @@ -import createFlash from '~/flash'; -import axios from '~/lib/utils/axios_utils'; -import { backOff } from '~/lib/utils/common_utils'; -import statusCodes from '~/lib/utils/http_status'; -import { __ } from '~/locale'; -import { MAX_REQUESTS, CHECKING_INSTALLED, TIMEOUT } from '../constants'; -import * as types from './mutation_types'; - -export const requestFunctionsLoading = ({ commit }) => commit(types.REQUEST_FUNCTIONS_LOADING); -export const receiveFunctionsSuccess = ({ commit }, data) => - commit(types.RECEIVE_FUNCTIONS_SUCCESS, data); -export const receiveFunctionsPartial = ({ commit }, data) => - commit(types.RECEIVE_FUNCTIONS_PARTIAL, data); -export const receiveFunctionsTimeout = ({ commit }, data) => - commit(types.RECEIVE_FUNCTIONS_TIMEOUT, data); -export const receiveFunctionsNoDataSuccess = ({ commit }, data) => - commit(types.RECEIVE_FUNCTIONS_NODATA_SUCCESS, data); -export const receiveFunctionsError = ({ commit }, error) => - commit(types.RECEIVE_FUNCTIONS_ERROR, error); - -export const receiveMetricsSuccess = ({ commit }, data) => - commit(types.RECEIVE_METRICS_SUCCESS, data); -export const receiveMetricsNoPrometheus = ({ commit }) => - commit(types.RECEIVE_METRICS_NO_PROMETHEUS); -export const receiveMetricsNoDataSuccess = ({ commit }, data) => - commit(types.RECEIVE_METRICS_NODATA_SUCCESS, data); -export const receiveMetricsError = ({ commit }, error) => - commit(types.RECEIVE_METRICS_ERROR, error); - -export const fetchFunctions = ({ dispatch }, { functionsPath }) => { - let retryCount = 0; - - const functionsPartiallyFetched = (data) => { - if (data.functions !== null && data.functions.length) { - dispatch('receiveFunctionsPartial', data); - } - }; - - dispatch('requestFunctionsLoading'); - - backOff((next, stop) => { - axios - .get(functionsPath) - .then((response) => { - if (response.data.knative_installed === CHECKING_INSTALLED) { - retryCount += 1; - if (retryCount < MAX_REQUESTS) { - functionsPartiallyFetched(response.data); - next(); - } else { - stop(TIMEOUT); - } - } else { - stop(response.data); - } - }) - .catch(stop); - }) - .then((data) => { - if (data === TIMEOUT) { - dispatch('receiveFunctionsTimeout'); - createFlash({ - message: __('Loading functions timed out. Please reload the page to try again.'), - }); - } else if (data.functions !== null && data.functions.length) { - dispatch('receiveFunctionsSuccess', data); - } else { - dispatch('receiveFunctionsNoDataSuccess', data); - } - }) - .catch((error) => { - dispatch('receiveFunctionsError', error); - createFlash({ - message: error, - }); - }); -}; - -export const fetchMetrics = ({ dispatch }, { metricsPath, hasPrometheus }) => { - let retryCount = 0; - - if (!hasPrometheus) { - dispatch('receiveMetricsNoPrometheus'); - return; - } - - backOff((next, stop) => { - axios - .get(metricsPath) - .then((response) => { - if (response.status === statusCodes.NO_CONTENT) { - retryCount += 1; - if (retryCount < MAX_REQUESTS) { - next(); - } else { - dispatch('receiveMetricsNoDataSuccess'); - stop(null); - } - } else { - stop(response.data); - } - }) - .catch(stop); - }) - .then((data) => { - if (data === null) { - return; - } - - const updatedMetric = data.metrics; - const queries = data.metrics.queries.map((query) => ({ - ...query, - result: query.result.map((result) => ({ - ...result, - values: result.values.map(([timestamp, value]) => ({ - time: new Date(timestamp * 1000).toISOString(), - value: Number(value), - })), - })), - })); - - updatedMetric.queries = queries; - dispatch('receiveMetricsSuccess', updatedMetric); - }) - .catch((error) => { - dispatch('receiveMetricsError', error); - createFlash({ - message: error, - }); - }); -}; diff --git a/app/assets/javascripts/serverless/store/getters.js b/app/assets/javascripts/serverless/store/getters.js deleted file mode 100644 index da975c56e5d..00000000000 --- a/app/assets/javascripts/serverless/store/getters.js +++ /dev/null @@ -1,7 +0,0 @@ -import { translate } from '../utils'; - -export const hasPrometheusMissingData = (state) => state.hasPrometheus && !state.hasPrometheusData; - -// Convert the function list into a k/v grouping based on the environment scope - -export const getFunctions = (state) => translate(state.functions); diff --git a/app/assets/javascripts/serverless/store/index.js b/app/assets/javascripts/serverless/store/index.js deleted file mode 100644 index 6f32d85201e..00000000000 --- a/app/assets/javascripts/serverless/store/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; -import * as actions from './actions'; -import * as getters from './getters'; -import mutations from './mutations'; -import createState from './state'; - -Vue.use(Vuex); - -export const createStore = (entryPointData = {}) => - new Vuex.Store({ - actions, - getters, - mutations, - state: createState(entryPointData), - }); - -export default createStore; diff --git a/app/assets/javascripts/serverless/store/mutation_types.js b/app/assets/javascripts/serverless/store/mutation_types.js deleted file mode 100644 index b8fa9ea1a01..00000000000 --- a/app/assets/javascripts/serverless/store/mutation_types.js +++ /dev/null @@ -1,11 +0,0 @@ -export const REQUEST_FUNCTIONS_LOADING = 'REQUEST_FUNCTIONS_LOADING'; -export const RECEIVE_FUNCTIONS_SUCCESS = 'RECEIVE_FUNCTIONS_SUCCESS'; -export const RECEIVE_FUNCTIONS_PARTIAL = 'RECEIVE_FUNCTIONS_PARTIAL'; -export const RECEIVE_FUNCTIONS_TIMEOUT = 'RECEIVE_FUNCTIONS_TIMEOUT'; -export const RECEIVE_FUNCTIONS_NODATA_SUCCESS = 'RECEIVE_FUNCTIONS_NODATA_SUCCESS'; -export const RECEIVE_FUNCTIONS_ERROR = 'RECEIVE_FUNCTIONS_ERROR'; - -export const RECEIVE_METRICS_NO_PROMETHEUS = 'RECEIVE_METRICS_NO_PROMETHEUS'; -export const RECEIVE_METRICS_SUCCESS = 'RECEIVE_METRICS_SUCCESS'; -export const RECEIVE_METRICS_NODATA_SUCCESS = 'RECEIVE_METRICS_NODATA_SUCCESS'; -export const RECEIVE_METRICS_ERROR = 'RECEIVE_METRICS_ERROR'; diff --git a/app/assets/javascripts/serverless/store/mutations.js b/app/assets/javascripts/serverless/store/mutations.js deleted file mode 100644 index 2685a5b11ff..00000000000 --- a/app/assets/javascripts/serverless/store/mutations.js +++ /dev/null @@ -1,49 +0,0 @@ -import * as types from './mutation_types'; - -export default { - [types.REQUEST_FUNCTIONS_LOADING](state) { - state.isLoading = true; - }, - [types.RECEIVE_FUNCTIONS_SUCCESS](state, data) { - state.functions = data.functions; - state.installed = data.knative_installed; - state.isLoading = false; - state.hasFunctionData = true; - }, - [types.RECEIVE_FUNCTIONS_PARTIAL](state, data) { - state.functions = data.functions; - state.installed = true; - state.isLoading = true; - state.hasFunctionData = true; - }, - [types.RECEIVE_FUNCTIONS_TIMEOUT](state) { - state.isLoading = false; - }, - [types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state, data) { - state.isLoading = false; - state.installed = data.knative_installed; - state.hasFunctionData = false; - }, - [types.RECEIVE_FUNCTIONS_ERROR](state, error) { - state.error = error; - state.hasFunctionData = false; - state.isLoading = false; - }, - [types.RECEIVE_METRICS_SUCCESS](state, data) { - state.isLoading = false; - state.hasPrometheusData = true; - state.graphData = data; - }, - [types.RECEIVE_METRICS_NODATA_SUCCESS](state) { - state.isLoading = false; - state.hasPrometheusData = false; - }, - [types.RECEIVE_METRICS_ERROR](state, error) { - state.hasPrometheusData = false; - state.error = error; - }, - [types.RECEIVE_METRICS_NO_PROMETHEUS](state) { - state.hasPrometheusData = false; - state.hasPrometheus = false; - }, -}; diff --git a/app/assets/javascripts/serverless/store/state.js b/app/assets/javascripts/serverless/store/state.js deleted file mode 100644 index 353bfcf3fed..00000000000 --- a/app/assets/javascripts/serverless/store/state.js +++ /dev/null @@ -1,22 +0,0 @@ -export default ( - initialState = { clustersPath: null, helpPath: null, emptyImagePath: null, statusPath: null }, -) => ({ - clustersPath: initialState.clustersPath, - error: null, - helpPath: initialState.helpPath, - installed: 'checking', - isLoading: true, - - // functions - functions: [], - hasFunctionData: true, - statusPath: initialState.statusPath, - - // function_details - hasPrometheus: true, - hasPrometheusData: false, - graphData: {}, - - // empty_state - emptyImagePath: initialState.emptyImagePath, -}); diff --git a/app/assets/javascripts/serverless/utils.js b/app/assets/javascripts/serverless/utils.js deleted file mode 100644 index e218a9aa3fd..00000000000 --- a/app/assets/javascripts/serverless/utils.js +++ /dev/null @@ -1,20 +0,0 @@ -// Validate that the object coming in has valid query details and results -export const validateGraphData = (data) => - data.queries && - Array.isArray(data.queries) && - data.queries.filter((query) => { - if (Array.isArray(query.result)) { - return query.result.filter((res) => Array.isArray(res.values)).length === query.result.length; - } - - return false; - }).length === data.queries.length; - -export const translate = (functions) => - functions.reduce( - (acc, func) => - Object.assign(acc, { - [func.environment_scope]: (acc[func.environment_scope] || []).concat([func]), - }), - {}, - ); diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue index 19f588b28be..e9c68008143 100644 --- a/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue +++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_participant.vue @@ -3,6 +3,11 @@ import { GlAvatarLabeled, GlAvatarLink, GlIcon } from '@gitlab/ui'; import { IssuableType } from '~/issues/constants'; import { s__, sprintf } from '~/locale'; +const AVAILABILITY_STATUS = { + NOT_SET: 'NOT_SET', + BUSY: 'BUSY', +}; + export default { components: { GlAvatarLabeled, @@ -22,12 +27,17 @@ export default { }, computed: { userLabel() { - if (!this.user.status) { - return this.user.name; + const { name, status } = this.user; + if (!status || status?.availability !== AVAILABILITY_STATUS.BUSY) { + return name; } - return sprintf(s__('UserAvailability|%{author} (Busy)'), { - author: this.user.name, - }); + return sprintf( + s__('UserAvailability|%{author} (Busy)'), + { + author: name, + }, + false, + ); }, hasCannotMergeIcon() { return this.issuableType === IssuableType.MergeRequest && !this.user.canMerge; diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb index c9b6e8923fe..eaa945b0312 100644 --- a/app/controllers/concerns/uploads_actions.rb +++ b/app/controllers/concerns/uploads_actions.rb @@ -143,11 +143,17 @@ module UploadsActions end def bypass_auth_checks_on_uploads? - if ::Feature.enabled?(:enforce_auth_checks_on_uploads, project, default_enabled: :yaml) - false - else - action_name == 'show' && embeddable? + if ::Feature.enabled?(:enforce_auth_checks_on_uploads, target_project, default_enabled: :yaml) + if target_project && !target_project.public? && target_project.enforce_auth_checks_on_uploads? + return false + end end + + action_name == 'show' && embeddable? + end + + def target_project + nil end def find_model diff --git a/app/controllers/projects/serverless/functions_controller.rb b/app/controllers/projects/serverless/functions_controller.rb deleted file mode 100644 index d61694cf40f..00000000000 --- a/app/controllers/projects/serverless/functions_controller.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -module Projects - module Serverless - class FunctionsController < Projects::ApplicationController - before_action :ensure_feature_enabled! - before_action :authorize_read_cluster! - - feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned - urgency :low - - def index - respond_to do |format| - format.json do - functions = finder.execute.select do |function| - can?(@current_user, :read_cluster, function.cluster) - end - - serialized_functions = serialize_function(functions) - - render json: { - knative_installed: finder.knative_installed, - functions: serialized_functions - }.to_json - end - - format.html do - render - end - end - end - - def show - function = finder.service(params[:environment_id], params[:id]) - return not_found unless function && can?(@current_user, :read_cluster, function.cluster) - - @service = serialize_function(function) - return not_found if @service.nil? - - @prometheus = finder.has_prometheus?(params[:environment_id]) - - respond_to do |format| - format.json do - render json: @service - end - - format.html - end - end - - def metrics - respond_to do |format| - format.json do - metrics = finder.invocation_metrics(params[:environment_id], params[:id]) - - if metrics.nil? - head :no_content - else - render json: metrics - end - end - end - end - - private - - def finder - Projects::Serverless::FunctionsFinder.new(project) - end - - def serialize_function(function) - Projects::Serverless::ServiceSerializer.new(current_user: @current_user, project: project).represent(function) - end - - def ensure_feature_enabled! - render_404 unless Feature.enabled?(:deprecated_serverless, project, default_enabled: :yaml, type: :ops) - end - end - end -end diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb index e6e91231ba2..a364668ea5f 100644 --- a/app/controllers/projects/uploads_controller.rb +++ b/app/controllers/projects/uploads_controller.rb @@ -23,6 +23,10 @@ class Projects::UploadsController < Projects::ApplicationController FileUploader end + def target_project + model + end + def find_model return @project if @project diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 9957f8c1451..cce02d493f0 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -419,6 +419,7 @@ class ProjectsController < Projects::ApplicationController squash_option mr_default_target_self warn_about_potentially_unwanted_characters + enforce_auth_checks_on_uploads ] end diff --git a/app/finders/projects/serverless/functions_finder.rb b/app/finders/projects/serverless/functions_finder.rb deleted file mode 100644 index f8ccea6b820..00000000000 --- a/app/finders/projects/serverless/functions_finder.rb +++ /dev/null @@ -1,153 +0,0 @@ -# frozen_string_literal: true - -module Projects - module Serverless - class FunctionsFinder - include Gitlab::Utils::StrongMemoize - include ReactiveCaching - - attr_reader :project - - self.reactive_cache_key = ->(finder) { finder.cache_key } - self.reactive_cache_work_type = :external_dependency - self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) } - - MAX_CLUSTERS = 10 - - def initialize(project) - @project = project - end - - def execute - knative_services.flatten.compact - end - - def knative_installed - return knative_installed_from_cluster?(*cache_key) if available_environments.empty? - - states = services_finders.map do |finder| - finder.knative_detected.tap do |state| - return state if state == ::Clusters::KnativeServicesFinder::KNATIVE_STATES['checking'] # rubocop:disable Cop/AvoidReturnFromBlocks - end - end - - states.any? { |state| state == ::Clusters::KnativeServicesFinder::KNATIVE_STATES['installed'] } - end - - def service(environment_scope, name) - knative_service(environment_scope, name)&.first - end - - def invocation_metrics(environment_scope, name) - environment = finders_for_scope(environment_scope).first&.environment - - if environment.present? && environment.prometheus_adapter&.can_query? - func = ::Serverless::Function.new(project, name, environment.deployment_namespace) - environment.prometheus_adapter.query(:knative_invocation, func) - end - end - - def has_prometheus?(environment_scope) - finders_for_scope(environment_scope).any? do |finder| - finder.cluster.integration_prometheus_available? - end - end - - def self.from_cache(project_id) - project = Project.find(project_id) - - new(project) - end - - def cache_key(*args) - [project.id] - end - - def calculate_reactive_cache(*) - # rubocop: disable CodeReuse/ActiveRecord - project.all_clusters.enabled.take(MAX_CLUSTERS).any? do |cluster| - cluster.kubeclient.knative_client.discover - rescue Kubeclient::ResourceNotFoundError - next - end - end - - private - - def knative_installed_from_cluster?(*cache_key) - cached_data = with_reactive_cache_memoized(*cache_key) { |data| data } - - return ::Clusters::KnativeServicesFinder::KNATIVE_STATES['checking'] if cached_data.nil? - - cached_data ? true : false - end - - def with_reactive_cache_memoized(*cache_key) - strong_memoize(:reactive_cache) do - with_reactive_cache(*cache_key) { |data| data } - end - end - - def knative_service(environment_scope, name) - finders_for_scope(environment_scope).map do |finder| - services = finder - .services - .select { |svc| svc["metadata"]["name"] == name } - - attributes = add_metadata(finder, services).first - next unless attributes - - Gitlab::Serverless::Service.new(attributes) - end - end - - def knative_services - services_finders.map do |finder| - attributes = add_metadata(finder, finder.services) - - attributes&.map do |attributes| - Gitlab::Serverless::Service.new(attributes) - end - end - end - - def add_metadata(finder, services) - return if services.nil? - - add_pod_count = services.one? - - services.each do |s| - s["environment_scope"] = finder.cluster.environment_scope - s["environment"] = finder.environment - s["cluster"] = finder.cluster - - if add_pod_count - s["podcount"] = finder - .service_pod_details(s["metadata"]["name"]) - .length - end - end - end - - def services_finders - strong_memoize(:services_finders) do - available_environments.map(&:knative_services_finder).compact - end - end - - def available_environments - @project.environments.available.preload_cluster - end - - def finders_for_scope(environment_scope) - services_finders.select do |finder| - environment_scope == finder.cluster.environment_scope - end - end - - def id - nil - end - end - end -end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index fd91418a742..64254956e49 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -614,6 +614,7 @@ module ProjectsHelper operationsAccessLevel: feature.operations_access_level, showDefaultAwardEmojis: project.show_default_award_emojis?, warnAboutPotentiallyUnwantedCharacters: project.warn_about_potentially_unwanted_characters?, + enforceAuthChecksOnUploads: project.enforce_auth_checks_on_uploads?, securityAndComplianceAccessLevel: project.security_and_compliance_access_level, containerRegistryAccessLevel: feature.container_registry_access_level } diff --git a/app/models/project.rb b/app/models/project.rb index 2b99d6c9d24..eeafb188fc4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -450,6 +450,7 @@ class Project < ApplicationRecord to: :project_feature, allow_nil: true alias_method :container_registry_enabled, :container_registry_enabled? delegate :show_default_award_emojis, :show_default_award_emojis=, :show_default_award_emojis?, + :enforce_auth_checks_on_uploads, :enforce_auth_checks_on_uploads=, :enforce_auth_checks_on_uploads?, :warn_about_potentially_unwanted_characters, :warn_about_potentially_unwanted_characters=, :warn_about_potentially_unwanted_characters?, to: :project_setting, allow_nil: true delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?, diff --git a/app/views/projects/serverless/functions/index.html.haml b/app/views/projects/serverless/functions/index.html.haml deleted file mode 100644 index 03a1b0ee7bb..00000000000 --- a/app/views/projects/serverless/functions/index.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -- @content_class = "limit-container-width" unless fluid_layout -- breadcrumb_title _('Serverless') -- page_title _('Serverless') -- status_path = project_serverless_functions_path(@project, format: :json) -- clusters_path = project_clusters_path(@project) - -.serverless-functions-page.js-serverless-functions-page{ data: { status_path: status_path, - installed: @installed, - clusters_path: clusters_path, - help_path: help_page_path('user/project/clusters/serverless/index'), - empty_image_path: image_path('illustrations/empty-state/empty-serverless-lg.svg') } } - -.js-serverless-functions-notice - .flash-container - -.top-area.adjust.d-flex.justify-content-center.gl-border-none - .serverless-functions-table#js-serverless-functions diff --git a/app/views/projects/serverless/functions/show.html.haml b/app/views/projects/serverless/functions/show.html.haml deleted file mode 100644 index dd81d957e51..00000000000 --- a/app/views/projects/serverless/functions/show.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -- @content_class = "limit-container-width" unless fluid_layout -- clusters_path = project_clusters_path(@project) -- help_path = help_page_path('user/project/clusters/serverless/index') - -- add_to_breadcrumbs('Serverless', project_serverless_functions_path(@project)) - -- page_title @service[:name] - -.serverless-function-details-page.js-serverless-function-details-page{ data: { service: @service.as_json, - prometheus: @prometheus, - clusters_path: clusters_path, - help_path: help_path } } - -.serverless-function-details#js-serverless-function-details - -.js-serverless-function-notice - .flash-container - -.function-holder.js-function-holder.input-group diff --git a/config/feature_flags/ops/deprecated_serverless.yml b/config/feature_flags/ops/deprecated_serverless.yml deleted file mode 100644 index 0982778f139..00000000000 --- a/config/feature_flags/ops/deprecated_serverless.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: deprecated_serverless -introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81493' -rollout_issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/353901' -milestone: '14.9' -type: ops -group: 'group::configure' -default_enabled: true diff --git a/config/routes/project.rb b/config/routes/project.rb index 0ef891aa4c9..6b2a9700686 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -303,15 +303,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do concerns :clusterable - namespace :serverless do - scope :functions do - get '/:environment_id/:id', to: 'functions#show' - get '/:environment_id/:id/metrics', to: 'functions#metrics', as: :metrics - end - - resources :functions, only: [:index] - end - resources :terraform, only: [:index] resources :google_cloud, only: [:index] diff --git a/data/removals/15_0/15-0-serverless.yml b/data/removals/15_0/15-0-serverless.yml new file mode 100644 index 00000000000..9f6a94037d8 --- /dev/null +++ b/data/removals/15_0/15-0-serverless.yml @@ -0,0 +1,17 @@ +- name: "GitLab Serverless" + announcement_milestone: "14.3" + announcement_date: "2021-09-22" + removal_milestone: "15.0" + removal_date: "2022-05-22" + breaking_change: true + reporter: nagyv-gitlab + body: | # Do not modify this line, instead modify the lines below. + All functionality related to GitLab Serverless was deprecated in GitLab 14.3 and is scheduled for removal in GitLab 15.0. Users who need a replacement for this functionality are encouraged to explore using the following technologies with GitLab CI/CD: + + - [Serverless Framework](https://www.serverless.com) + - [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/deploying-using-gitlab.html) + + For additional context, or to provide feedback regarding this change, please reference our [deprecation issue](https://gitlab.com/groups/gitlab-org/configure/-/epics/6). +# The following items are not published on the docs page, but may be used in the future. + stage: "Configure" + issue_url: https://gitlab.com/groups/gitlab-org/configure/-/epics/6 diff --git a/db/migrate/20220324091224_add_enforce_auth_checks_on_uploads_to_project_settings.rb b/db/migrate/20220324091224_add_enforce_auth_checks_on_uploads_to_project_settings.rb new file mode 100644 index 00000000000..2c86d1d346d --- /dev/null +++ b/db/migrate/20220324091224_add_enforce_auth_checks_on_uploads_to_project_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddEnforceAuthChecksOnUploadsToProjectSettings < Gitlab::Database::Migration[1.0] + enable_lock_retries! + + def change + add_column :project_settings, :enforce_auth_checks_on_uploads, :boolean, null: false, default: true + end +end diff --git a/db/schema_migrations/20220324091224 b/db/schema_migrations/20220324091224 new file mode 100644 index 00000000000..4be0647a91b --- /dev/null +++ b/db/schema_migrations/20220324091224 @@ -0,0 +1 @@ +7418b98f33ada13dedab493ad8a969808a18db2fa0188e428b1c685aabb3bc66 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 6cab0d2bf13..a51fdf660dd 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -19468,6 +19468,7 @@ CREATE TABLE project_settings ( squash_commit_template text, legacy_open_source_license_available boolean DEFAULT true NOT NULL, target_platforms character varying[] DEFAULT '{}'::character varying[] NOT NULL, + enforce_auth_checks_on_uploads boolean DEFAULT true NOT NULL, CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)), CONSTRAINT check_b09644994b CHECK ((char_length(squash_commit_template) <= 500)), CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL)), diff --git a/doc/administration/git_protocol.md b/doc/administration/git_protocol.md index 29156d9b3e1..3612487f456 100644 --- a/doc/administration/git_protocol.md +++ b/doc/administration/git_protocol.md @@ -111,4 +111,4 @@ URL to use SSH. ### Observe Git protocol version of connections For information on observing the Git protocol versions are being used in a production environment, -see the [relevant documentation](gitaly/index.md#useful-queries). +see the [relevant documentation](gitaly/monitoring.md#useful-queries). diff --git a/doc/administration/gitaly/configure_gitaly.md b/doc/administration/gitaly/configure_gitaly.md index 8746afd60ba..e128682af75 100644 --- a/doc/administration/gitaly/configure_gitaly.md +++ b/doc/administration/gitaly/configure_gitaly.md @@ -2,7 +2,6 @@ stage: Create group: Gitaly info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments -type: reference --- # Configure Gitaly **(FREE SELF)** @@ -710,7 +709,7 @@ To configure Gitaly with TLS: ### Observe type of Gitaly connections For information on observing the type of Gitaly connections being served, see the -[relevant documentation](index.md#useful-queries). +[relevant documentation](monitoring.md#useful-queries). ## `gitaly-ruby` @@ -816,7 +815,7 @@ repository. In the example above: - If the queue grows beyond 10, subsequent requests are rejected with an error. You can observe the behavior of this queue using the Gitaly logs and Prometheus. For more -information, see the [relevant documentation](index.md#monitor-gitaly-concurrency-limiting). +information, see the [relevant documentation](monitoring.md#monitor-gitaly-concurrency-limiting). ## Background Repository Optimization @@ -871,7 +870,7 @@ server" and "Gitaly client" refers to the same machine. ### Verify authentication monitoring Before rotating a Gitaly authentication token, verify that you can -[monitor the authentication behavior](index.md#useful-queries) of your GitLab installation using +[monitor the authentication behavior](monitoring.md#useful-queries) of your GitLab installation using Prometheus. You can then continue the rest of the procedure. @@ -1081,7 +1080,7 @@ closed it. ### Observe the cache -The cache can be observed [using metrics](index.md#pack-objects-cache) and in the following logged +The cache can be observed [using metrics](monitoring.md#pack-objects-cache) and in the following logged information: |Message|Fields|Description| diff --git a/doc/administration/gitaly/faq.md b/doc/administration/gitaly/faq.md index b0a88e8adc9..a5c2c7d1469 100644 --- a/doc/administration/gitaly/faq.md +++ b/doc/administration/gitaly/faq.md @@ -2,7 +2,6 @@ stage: Create group: Gitaly info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments -type: reference --- # Frequently asked questions **(FREE SELF)** diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md index 0316efa92bf..1660269683d 100644 --- a/doc/administration/gitaly/index.md +++ b/doc/administration/gitaly/index.md @@ -2,7 +2,6 @@ stage: Create group: Gitaly info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments -type: reference --- # Gitaly and Gitaly Cluster **(FREE SELF)** @@ -318,7 +317,7 @@ The primary node is chosen to serve the request if: - There are no up to date nodes. - Any other error occurs during node selection. -You can [monitor distribution of reads](#monitor-gitaly-cluster) using Prometheus. +You can [monitor distribution of reads](monitoring.md#monitor-gitaly-cluster) using Prometheus. #### Strong consistency @@ -346,7 +345,7 @@ Strong consistency: - The [13.12 documentation](https://docs.gitlab.com/13.12/ee/administration/gitaly/praefect.html#strong-consistency). - Is unavailable in GitLab 13.0 and earlier. -For more information on monitoring strong consistency, see the Gitaly Cluster [Prometheus metrics documentation](#monitor-gitaly-cluster). +For more information on monitoring strong consistency, see the Gitaly Cluster [Prometheus metrics documentation](monitoring.md#monitor-gitaly-cluster). #### Replication factor @@ -394,196 +393,6 @@ off Gitaly Cluster to a sharded Gitaly instance: 1. [Move the repositories](../operations/moving_repositories.md#move-repositories) to the newly created storage. You can move them by shard or by group, which gives you the opportunity to spread them over multiple Gitaly servers. -## Monitor Gitaly and Gitaly Cluster - -You can use the available logs and [Prometheus metrics](../monitoring/prometheus/index.md) to -monitor Gitaly and Gitaly Cluster (Praefect). - -Metric definitions are available: - -- Directly from Prometheus `/metrics` endpoint configured for Gitaly. -- Using [Grafana Explore](https://grafana.com/docs/grafana/latest/explore/) on a - Grafana instance configured against Prometheus. - -### Monitor Gitaly rate limiting - -Gitaly can be configured to limit requests based on: - -- Concurrency of requests. -- A rate limit. - -Monitor Gitaly request limiting with the `gitaly_requests_dropped_total` Prometheus metric. This metric provides a total count -of requests dropped due to request limiting. The `reason` label indicates why a request was dropped: - -- `rate`, due to rate limiting. -- `max_size`, because the concurrency queue size was reached. -- `max_time`, because the request exceeded the maximum queue wait time as configured in Gitaly. - -### Monitor Gitaly concurrency limiting - -You can observe specific behavior of [concurrency-queued requests](configure_gitaly.md#limit-rpc-concurrency) using -the Gitaly logs and Prometheus: - -- In the [Gitaly logs](../logs.md#gitaly-logs), look for the string (or structured log field) - `acquire_ms`. Messages that have this field are reporting about the concurrency limiter. -- In Prometheus, look for the following metrics: - - `gitaly_concurrency_limiting_in_progress` indicates how many concurrent requests are - being processed. - - `gitaly_concurrency_limiting_queued` indicates how many requests for an RPC for a given - repository are waiting due to the concurrency limit being reached. - - `gitaly_concurrency_limiting_acquiring_seconds` indiciates how long a request has to - wait due to concurrency limits before being processed. - -### `pack-objects` cache - -The following [`pack-objects` cache](configure_gitaly.md#pack-objects-cache) metrics are available: - -- `gitaly_pack_objects_cache_enabled`, a gauge set to `1` when the cache is enabled. Available - labels: `dir` and `max_age`. -- `gitaly_pack_objects_cache_lookups_total`, a counter for cache lookups. Available label: `result`. -- `gitaly_pack_objects_generated_bytes_total`, a counter for the number of bytes written into the - cache. -- `gitaly_pack_objects_served_bytes_total`, a counter for the number of bytes read from the cache. -- `gitaly_streamcache_filestore_disk_usage_bytes`, a gauge for the total size of cache files. - Available label: `dir`. -- `gitaly_streamcache_index_entries`, a gauge for the number of entries in the cache. Available - label: `dir`. - -Some of these metrics start with `gitaly_streamcache` because they are generated by the -`streamcache` internal library package in Gitaly. - -Example: - -```plaintext -gitaly_pack_objects_cache_enabled{dir="/var/opt/gitlab/git-data/repositories/+gitaly/PackObjectsCache",max_age="300"} 1 -gitaly_pack_objects_cache_lookups_total{result="hit"} 2 -gitaly_pack_objects_cache_lookups_total{result="miss"} 1 -gitaly_pack_objects_generated_bytes_total 2.618649e+07 -gitaly_pack_objects_served_bytes_total 7.855947e+07 -gitaly_streamcache_filestore_disk_usage_bytes{dir="/var/opt/gitlab/git-data/repositories/+gitaly/PackObjectsCache"} 2.6200152e+07 -gitaly_streamcache_filestore_removed_total{dir="/var/opt/gitlab/git-data/repositories/+gitaly/PackObjectsCache"} 1 -gitaly_streamcache_index_entries{dir="/var/opt/gitlab/git-data/repositories/+gitaly/PackObjectsCache"} 1 -``` - -### Useful queries - -The following are useful queries for monitoring Gitaly: - -- Use the following Prometheus query to observe the - [type of connections](configure_gitaly.md#enable-tls-support) Gitaly is serving a production - environment: - - ```prometheus - sum(rate(gitaly_connections_total[5m])) by (type) - ``` - -- Use the following Prometheus query to monitor the - [authentication behavior](configure_gitaly.md#observe-type-of-gitaly-connections) of your GitLab - installation: - - ```prometheus - sum(rate(gitaly_authentications_total[5m])) by (enforced, status) - ``` - - In a system where authentication is configured correctly and where you have live traffic, you - see something like this: - - ```prometheus - {enforced="true",status="ok"} 4424.985419441742 - ``` - - There may also be other numbers with rate 0, but you only have to take note of the non-zero numbers. - - The only non-zero number should have `enforced="true",status="ok"`. If you have other non-zero - numbers, something is wrong in your configuration. - - The `status="ok"` number reflects your current request rate. In the example above, Gitaly is - handling about 4000 requests per second. - -- Use the following Prometheus query to observe the [Git protocol versions](../git_protocol.md) - being used in a production environment: - - ```prometheus - sum(rate(gitaly_git_protocol_requests_total[1m])) by (grpc_method,git_protocol,grpc_service) - ``` - -### Monitor Gitaly Cluster - -To monitor Gitaly Cluster (Praefect), you can use these Prometheus metrics. There are two separate metrics -endpoints from which metrics can be scraped: - -- The default `/metrics` endpoint. -- `/db_metrics`, which contains metrics that require database queries. - -#### Default Prometheus `/metrics` endpoint - -The following metrics are available from the `/metrics` endpoint: - -- `gitaly_praefect_read_distribution`, a counter to track [distribution of reads](#distributed-reads). - It has two labels: - - - `virtual_storage`. - - `storage`. - - They reflect configuration defined for this instance of Praefect. - -- `gitaly_praefect_replication_latency_bucket`, a histogram measuring the amount of time it takes - for replication to complete after the replication job starts. Available in GitLab 12.10 and later. -- `gitaly_praefect_replication_delay_bucket`, a histogram measuring how much time passes between - when the replication job is created and when it starts. Available in GitLab 12.10 and later. -- `gitaly_praefect_node_latency_bucket`, a histogram measuring the latency in Gitaly returning - health check information to Praefect. This indicates Praefect connection saturation. Available in - GitLab 12.10 and later. - -To monitor [strong consistency](#strong-consistency), you can use the following Prometheus metrics: - -- `gitaly_praefect_transactions_total`, the number of transactions created and voted on. -- `gitaly_praefect_subtransactions_per_transaction_total`, the number of times nodes cast a vote for - a single transaction. This can happen multiple times if multiple references are getting updated in - a single transaction. -- `gitaly_praefect_voters_per_transaction_total`: the number of Gitaly nodes taking part in a - transaction. -- `gitaly_praefect_transactions_delay_seconds`, the server-side delay introduced by waiting for the - transaction to be committed. -- `gitaly_hook_transaction_voting_delay_seconds`, the client-side delay introduced by waiting for - the transaction to be committed. - -To monitor the number of repositories that have no healthy, up-to-date replicas: - -- `gitaly_praefect_unavailable_repositories` - -To monitor [repository verification](praefect.md#repository-verification), use the following Prometheus metrics: - -- `gitaly_praefect_verification_queue_depth`, the total number of replicas pending verification. This - metric is scraped from the database and is only available when Prometheus is scraping the database metrics. -- `gitaly_praefect_verification_jobs_dequeued_total`, the number of verification jobs picked up by the - worker. -- `gitaly_praefect_verification_jobs_completed_total`, the number of verification jobs completed by the - worker. The `result` label indicates the end result of the jobs: - - `valid` indicates the expected replica existed on the storage. - - `invalid` indicates the replica expected to exist did not exist on the storage. - - `error` indicates the job failed and has to be retried. -- `gitaly_praefect_stale_verification_leases_released_total`, the number of stale verification leases - released. - -You can also monitor the [Praefect logs](../logs.md#praefect-logs). - -#### Database metrics `/db_metrics` endpoint - -> [Introduced](https://gitlab.com/gitlab-org/gitaly/-/issues/3286) in GitLab 14.5. - -The following metrics are available from the `/db_metrics` endpoint: - -- `gitaly_praefect_unavailable_repositories`, the number of repositories that have no healthy, up to date replicas. -- `gitaly_praefect_read_only_repositories`, the number of repositories in read-only mode in a virtual storage. - This metric is available for backwards compatibility reasons. `gitaly_praefect_unavailable_repositories` is more - accurate. -- `gitaly_praefect_replication_queue_depth`, the number of jobs in the replication queue. - -## Recover from failure - -Gitaly Cluster can [recover from certain types of failure](recovery.md). - ## Do not bypass Gitaly GitLab doesn't advise directly accessing Gitaly repositories stored on disk with a Git client, diff --git a/doc/administration/gitaly/monitoring.md b/doc/administration/gitaly/monitoring.md new file mode 100644 index 00000000000..7a4f2026f3d --- /dev/null +++ b/doc/administration/gitaly/monitoring.md @@ -0,0 +1,191 @@ +--- +stage: Create +group: Gitaly +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +--- + +# Monitoring Gitaly and Gitaly Cluster + +You can use the available logs and [Prometheus metrics](../monitoring/prometheus/index.md) to +monitor Gitaly and Gitaly Cluster (Praefect). + +Metric definitions are available: + +- Directly from Prometheus `/metrics` endpoint configured for Gitaly. +- Using [Grafana Explore](https://grafana.com/docs/grafana/latest/explore/) on a + Grafana instance configured against Prometheus. + +## Monitor Gitaly rate limiting + +Gitaly can be configured to limit requests based on: + +- Concurrency of requests. +- A rate limit. + +Monitor Gitaly request limiting with the `gitaly_requests_dropped_total` Prometheus metric. This metric provides a total count +of requests dropped due to request limiting. The `reason` label indicates why a request was dropped: + +- `rate`, due to rate limiting. +- `max_size`, because the concurrency queue size was reached. +- `max_time`, because the request exceeded the maximum queue wait time as configured in Gitaly. + +## Monitor Gitaly concurrency limiting + +You can observe specific behavior of [concurrency-queued requests](configure_gitaly.md#limit-rpc-concurrency) using +the Gitaly logs and Prometheus: + +- In the [Gitaly logs](../logs.md#gitaly-logs), look for the string (or structured log field) + `acquire_ms`. Messages that have this field are reporting about the concurrency limiter. +- In Prometheus, look for the following metrics: + - `gitaly_concurrency_limiting_in_progress` indicates how many concurrent requests are + being processed. + - `gitaly_concurrency_limiting_queued` indicates how many requests for an RPC for a given + repository are waiting due to the concurrency limit being reached. + - `gitaly_concurrency_limiting_acquiring_seconds` indicates how long a request has to + wait due to concurrency limits before being processed. + +## `pack-objects` cache + +The following [`pack-objects` cache](configure_gitaly.md#pack-objects-cache) metrics are available: + +- `gitaly_pack_objects_cache_enabled`, a gauge set to `1` when the cache is enabled. Available + labels: `dir` and `max_age`. +- `gitaly_pack_objects_cache_lookups_total`, a counter for cache lookups. Available label: `result`. +- `gitaly_pack_objects_generated_bytes_total`, a counter for the number of bytes written into the + cache. +- `gitaly_pack_objects_served_bytes_total`, a counter for the number of bytes read from the cache. +- `gitaly_streamcache_filestore_disk_usage_bytes`, a gauge for the total size of cache files. + Available label: `dir`. +- `gitaly_streamcache_index_entries`, a gauge for the number of entries in the cache. Available + label: `dir`. + +Some of these metrics start with `gitaly_streamcache` because they are generated by the +`streamcache` internal library package in Gitaly. + +Example: + +```plaintext +gitaly_pack_objects_cache_enabled{dir="/var/opt/gitlab/git-data/repositories/+gitaly/PackObjectsCache",max_age="300"} 1 +gitaly_pack_objects_cache_lookups_total{result="hit"} 2 +gitaly_pack_objects_cache_lookups_total{result="miss"} 1 +gitaly_pack_objects_generated_bytes_total 2.618649e+07 +gitaly_pack_objects_served_bytes_total 7.855947e+07 +gitaly_streamcache_filestore_disk_usage_bytes{dir="/var/opt/gitlab/git-data/repositories/+gitaly/PackObjectsCache"} 2.6200152e+07 +gitaly_streamcache_filestore_removed_total{dir="/var/opt/gitlab/git-data/repositories/+gitaly/PackObjectsCache"} 1 +gitaly_streamcache_index_entries{dir="/var/opt/gitlab/git-data/repositories/+gitaly/PackObjectsCache"} 1 +``` + +## Useful queries + +The following are useful queries for monitoring Gitaly: + +- Use the following Prometheus query to observe the + [type of connections](configure_gitaly.md#enable-tls-support) Gitaly is serving a production + environment: + + ```prometheus + sum(rate(gitaly_connections_total[5m])) by (type) + ``` + +- Use the following Prometheus query to monitor the + [authentication behavior](configure_gitaly.md#observe-type-of-gitaly-connections) of your GitLab + installation: + + ```prometheus + sum(rate(gitaly_authentications_total[5m])) by (enforced, status) + ``` + + In a system where authentication is configured correctly and where you have live traffic, you + see something like this: + + ```prometheus + {enforced="true",status="ok"} 4424.985419441742 + ``` + + There may also be other numbers with rate 0, but you only have to take note of the non-zero numbers. + + The only non-zero number should have `enforced="true",status="ok"`. If you have other non-zero + numbers, something is wrong in your configuration. + + The `status="ok"` number reflects your current request rate. In the example above, Gitaly is + handling about 4000 requests per second. + +- Use the following Prometheus query to observe the [Git protocol versions](../git_protocol.md) + being used in a production environment: + + ```prometheus + sum(rate(gitaly_git_protocol_requests_total[1m])) by (grpc_method,git_protocol,grpc_service) + ``` + +## Monitor Gitaly Cluster + +To monitor Gitaly Cluster (Praefect), you can use these Prometheus metrics. There are two separate metrics +endpoints from which metrics can be scraped: + +- The default `/metrics` endpoint. +- `/db_metrics`, which contains metrics that require database queries. + +### Default Prometheus `/metrics` endpoint + +The following metrics are available from the `/metrics` endpoint: + +- `gitaly_praefect_read_distribution`, a counter to track [distribution of reads](index.md#distributed-reads). + It has two labels: + + - `virtual_storage`. + - `storage`. + + They reflect configuration defined for this instance of Praefect. + +- `gitaly_praefect_replication_latency_bucket`, a histogram measuring the amount of time it takes + for replication to complete after the replication job starts. Available in GitLab 12.10 and later. +- `gitaly_praefect_replication_delay_bucket`, a histogram measuring how much time passes between + when the replication job is created and when it starts. Available in GitLab 12.10 and later. +- `gitaly_praefect_node_latency_bucket`, a histogram measuring the latency in Gitaly returning + health check information to Praefect. This indicates Praefect connection saturation. Available in + GitLab 12.10 and later. + +To monitor [strong consistency](index.md#strong-consistency), you can use the following Prometheus metrics: + +- `gitaly_praefect_transactions_total`, the number of transactions created and voted on. +- `gitaly_praefect_subtransactions_per_transaction_total`, the number of times nodes cast a vote for + a single transaction. This can happen multiple times if multiple references are getting updated in + a single transaction. +- `gitaly_praefect_voters_per_transaction_total`: the number of Gitaly nodes taking part in a + transaction. +- `gitaly_praefect_transactions_delay_seconds`, the server-side delay introduced by waiting for the + transaction to be committed. +- `gitaly_hook_transaction_voting_delay_seconds`, the client-side delay introduced by waiting for + the transaction to be committed. + +To monitor the number of repositories that have no healthy, up-to-date replicas: + +- `gitaly_praefect_unavailable_repositories` + +To monitor [repository verification](praefect.md#repository-verification), use the following Prometheus metrics: + +- `gitaly_praefect_verification_queue_depth`, the total number of replicas pending verification. This + metric is scraped from the database and is only available when Prometheus is scraping the database metrics. +- `gitaly_praefect_verification_jobs_dequeued_total`, the number of verification jobs picked up by the + worker. +- `gitaly_praefect_verification_jobs_completed_total`, the number of verification jobs completed by the + worker. The `result` label indicates the end result of the jobs: + - `valid` indicates the expected replica existed on the storage. + - `invalid` indicates the replica expected to exist did not exist on the storage. + - `error` indicates the job failed and has to be retried. +- `gitaly_praefect_stale_verification_leases_released_total`, the number of stale verification leases + released. + +You can also monitor the [Praefect logs](../logs.md#praefect-logs). + +### Database metrics `/db_metrics` endpoint + +> [Introduced](https://gitlab.com/gitlab-org/gitaly/-/issues/3286) in GitLab 14.5. + +The following metrics are available from the `/db_metrics` endpoint: + +- `gitaly_praefect_unavailable_repositories`, the number of repositories that have no healthy, up to date replicas. +- `gitaly_praefect_read_only_repositories`, the number of repositories in read-only mode in a virtual storage. + This metric is available for backwards compatibility reasons. `gitaly_praefect_unavailable_repositories` is more + accurate. +- `gitaly_praefect_replication_queue_depth`, the number of jobs in the replication queue. diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md index 866488f2c56..bbb34a41f84 100644 --- a/doc/administration/gitaly/praefect.md +++ b/doc/administration/gitaly/praefect.md @@ -2,7 +2,6 @@ stage: Create group: Gitaly info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments -type: reference --- # Configure Gitaly Cluster **(FREE SELF)** diff --git a/doc/administration/gitaly/recovery.md b/doc/administration/gitaly/recovery.md index 0d659644bec..6de2acf1792 100644 --- a/doc/administration/gitaly/recovery.md +++ b/doc/administration/gitaly/recovery.md @@ -2,7 +2,6 @@ stage: Create group: Gitaly info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments -type: reference --- # Recovery options diff --git a/doc/administration/gitaly/reference.md b/doc/administration/gitaly/reference.md index 9fe09be10a3..d1e802111cd 100644 --- a/doc/administration/gitaly/reference.md +++ b/doc/administration/gitaly/reference.md @@ -2,7 +2,6 @@ stage: Create group: Gitaly info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments -type: reference --- # Gitaly reference **(FREE SELF)** @@ -71,7 +70,7 @@ Remember to disable `transitioning` when you are done changing your token settings. All authentication attempts are counted in Prometheus under -the [`gitaly_authentications_total` metric](index.md#useful-queries). +the [`gitaly_authentications_total` metric](monitoring.md#useful-queries). ### TLS diff --git a/doc/administration/gitaly/troubleshooting.md b/doc/administration/gitaly/troubleshooting.md index cefddad1b62..085bf399d27 100644 --- a/doc/administration/gitaly/troubleshooting.md +++ b/doc/administration/gitaly/troubleshooting.md @@ -2,7 +2,6 @@ stage: Create group: Gitaly info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments -type: reference --- # Troubleshooting Gitaly and Gitaly Cluster **(FREE SELF)** diff --git a/doc/administration/logs.md b/doc/administration/logs.md index f19a5e056c6..c89c485e7d3 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -1107,7 +1107,7 @@ in `/var/log/gitlab/gitlab-kas/`. For Omnibus GitLab installations, Praefect logs are in `/var/log/gitlab/praefect/`. -GitLab also tracks [Prometheus metrics for Praefect](gitaly/#monitor-gitaly-cluster). +GitLab also tracks [Prometheus metrics for Praefect](gitaly/monitoring.md#monitor-gitaly-cluster). ## Backup log diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 08f7d5095da..4f8fbd0c07e 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -156,7 +156,7 @@ The following metrics can be controlled by feature flags: ## Praefect metrics You can [configure Praefect](../../gitaly/praefect.md#praefect) to report metrics. For information -on available metrics, see the [relevant documentation](../../gitaly/index.md#monitor-gitaly-cluster). +on available metrics, see the [relevant documentation](../../gitaly/monitoring.md#monitor-gitaly-cluster). ## Sidekiq metrics diff --git a/doc/administration/package_information/postgresql_versions.md b/doc/administration/package_information/postgresql_versions.md index c80437221c4..fa23f8069ed 100644 --- a/doc/administration/package_information/postgresql_versions.md +++ b/doc/administration/package_information/postgresql_versions.md @@ -26,6 +26,7 @@ Read more about update policies and warnings in the PostgreSQL | GitLab version | PostgreSQL versions | Default version for fresh installs | Default version for upgrades | Notes | | -------------- | --------------------- | ---------------------------------- | ---------------------------- | ----- | +| 15.0 | 12.7, 13.3 | 13.3 | 13.3 | Users can manually upgrade to 13.3 following the [upgrade docs](https://docs.gitlab.com/omnibus/settings/database.html#gitlab-150-and-later). | | 14.1 | 12.7, 13.3 | 12.7 | 12.7 | PostgreSQL 13 available for fresh installations if not using [Geo](../geo/index.md#requirements-for-running-geo) or [Patroni](../postgresql/index.md#postgresql-replication-and-failover-with-omnibus-gitlab). | 14.0 | 12.7 | 12.7 | 12.7 | HA installations with repmgr are no longer supported and will be prevented from upgrading to Omnibus GitLab 14.0 | | 13.8 | 11.9, 12.4 | 12.4 | 12.4 | Package upgrades automatically performed PostgreSQL upgrade for nodes that are not part of a Geo or HA cluster.). | diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 0981d1e45d9..34fdf710bab 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -2666,7 +2666,7 @@ Input type: `ExternalAuditEventDestinationUpdateInput` | ---- | ---- | ----------- | | `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | `destinationUrl` | [`String`](#string) | Destination URL to change. | -| `id` | [`AuditEventsExternalAuditEventDestinationID!`](#auditeventsexternalauditeventdestinationid) | ID of external audit event destination to destroy. | +| `id` | [`AuditEventsExternalAuditEventDestinationID!`](#auditeventsexternalauditeventdestinationid) | ID of external audit event destination to update. | #### Fields diff --git a/doc/development/deprecation_guidelines/index.md b/doc/development/deprecation_guidelines/index.md index 6187a5a56ca..cafc40ccc68 100644 --- a/doc/development/deprecation_guidelines/index.md +++ b/doc/development/deprecation_guidelines/index.md @@ -35,3 +35,52 @@ For API removals, see the [GraphQL](../../api/graphql/index.md#deprecation-and-r For configuration removals, see the [Omnibus deprecation policy](../../administration/package_information/deprecation_policy.md). For versioning and upgrade details, see our [Release and Maintenance policy](../../policy/maintenance.md). + +## Update the deprecations and removals documentation + +The [deprecations](../../update/deprecations.md) and [removals](../../update/removals.md) +documentation is generated from the YAML files located in +[`gitlab/data/`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/data). + +To update the deprecations and removals pages when an entry is added, +edited, or removed: + +1. From the command line, navigate to your local clone of the [`gitlab-org/gitlab`](https://gitlab.com/gitlab-org/gitlab) project. +1. Create, edit, or remove the YAML file under [deprecations](https://gitlab.com/gitlab-org/gitlab/-/tree/master/data/deprecations) + or [removals](https://gitlab.com/gitlab-org/gitlab/-/tree/master/data/removals). +1. Compile the deprecation or removals documentation with the appropriate command: + + - For deprecations: + + ```shell + bin/rake gitlab:docs:compile_deprecations + ``` + + - For removals: + + ```shell + bin/rake gitlab:docs:compile_removals + ``` + +1. If needed, you can verify the docs are up to date with: + + - For deprecations: + + ```shell + bin/rake gitlab:docs:check_deprecations + ``` + + - For removals: + + ```shell + bin/rake gitlab:docs:check_removals + ``` + +1. Commit the updated documentation and push the changes. +1. Create a merge request using the [Deprecations](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/merge_request_templates/Deprecations.md) + or [Removals](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/merge_request_templates/Removals.md) templates. + +Related Handbook pages: + +- +- diff --git a/doc/update/index.md b/doc/update/index.md index b3c5532aae6..6558a289cdc 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -402,6 +402,12 @@ NOTE: Specific information that follow related to Ruby and Git versions do not apply to [Omnibus installations](https://docs.gitlab.com/omnibus/) and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with appropriate Ruby and Git versions and are not using system binaries for Ruby and Git. There is no need to install Ruby or Git when utilizing these two approaches. +### 14.10.0 + +- The upgrade to GitLab 14.10 executes a [concurrent index drop](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84308) of unneeded + entries from the `ci_job_artifacts` database table. This could potentially run for multiple minutes, especially if the table has a lot of + traffic and the migration is unable to acquire a lock. It is advised to let this process finish as restarting may result in data loss. + ### 14.9.0 - Database changes made by the upgrade to GitLab 14.9 can take hours or days to complete on larger GitLab instances. diff --git a/doc/update/removals.md b/doc/update/removals.md index 266b6e9fa7b..17bdf9f340d 100644 --- a/doc/update/removals.md +++ b/doc/update/removals.md @@ -61,6 +61,21 @@ The Container Registry supports [authentication](https://gitlab.com/gitlab-org/c Since it isn't used in the context of GitLab (the product), `htpasswd` authentication will be deprecated in GitLab 14.9 and removed in GitLab 15.0. +### GitLab Serverless + +WARNING: +This feature was changed or removed in 15.0 +as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes). +Before updating GitLab, review the details carefully to determine if you need to make any +changes to your code, settings, or workflow. + +All functionality related to GitLab Serverless was deprecated in GitLab 14.3 and is scheduled for removal in GitLab 15.0. Users who need a replacement for this functionality are encouraged to explore using the following technologies with GitLab CI/CD: + +- [Serverless Framework](https://www.serverless.com) +- [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/deploying-using-gitlab.html) + +For additional context, or to provide feedback regarding this change, please reference our [deprecation issue](https://gitlab.com/groups/gitlab-org/configure/-/epics/6). + ### GraphQL permissions change for Package settings WARNING: diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md index 73ee156dac1..277ce38aa3c 100644 --- a/doc/user/clusters/applications.md +++ b/doc/user/clusters/applications.md @@ -658,27 +658,12 @@ Here is an example configuration for Knative: domain: 'my.wildcard.A.record.dns' ``` -If you plan to use GitLab Serverless capabilities, be sure to set an `A record` -wildcard domain on your custom configuration. - Support for installing the Knative managed application is provided by the GitLab Configure group. If you run into unknown issues, [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new), and ping at least 2 people from the [Configure group](https://about.gitlab.com/handbook/product/categories/#configure-group). -#### Knative Metrics - -GitLab provides [Invocation Metrics](../project/clusters/serverless/index.md#invocation-metrics) -for your functions. To collect these metrics, you must have: - -1. Knative and Prometheus managed applications installed on your cluster. -1. Manually applied the custom metrics on your cluster by running the following command: - - ```shell - kubectl apply -f https://gitlab.com/gitlab-org/cluster-integration/cluster-applications/-/raw/02c8231e30ef5b6725e6ba368bc63863ceb3c07d/src/default-data/knative/istio-metrics.yaml - ``` - #### Uninstall Knative To uninstall Knative, you must first manually remove any custom metrics you have added diff --git a/doc/user/infrastructure/clusters/index.md b/doc/user/infrastructure/clusters/index.md index 114b4096091..238d7004a6f 100644 --- a/doc/user/infrastructure/clusters/index.md +++ b/doc/user/infrastructure/clusters/index.md @@ -53,7 +53,6 @@ the GitLab agent model on the [agent's blueprint documentation](../../../archite - [Cluster cost management](../../clusters/cost_management.md) - [Cluster environments](../../clusters/environments.md) - [Advanced traffic control with Canary Ingress](../../project/canary_deployments.md#advanced-traffic-control-with-canary-ingress-deprecated) -- [Serverless](../../project/clusters/serverless/index.md) - [Deploy Boards](../../project/deploy_boards.md) - [Pod logs](../../project/clusters/kubernetes_pod_logs.md) - [Clusters health](manage/clusters_health.md) diff --git a/doc/user/project/clusters/serverless/aws.md b/doc/user/project/clusters/serverless/aws.md index cf571abbf8a..93bc41dc24c 100644 --- a/doc/user/project/clusters/serverless/aws.md +++ b/doc/user/project/clusters/serverless/aws.md @@ -2,506 +2,10 @@ stage: Configure group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +remove_date: '2022-08-22' +redirect_to: '../../../../update/removals.md#gitlab-serverless' --- -# Deploying AWS Lambda function using GitLab CI/CD **(FREE)** +# Deploying AWS Lambda function using GitLab CI/CD (removed) **(FREE)** -GitLab allows users to easily deploy AWS Lambda functions and create rich serverless applications. - -GitLab supports deployment of AWS Lambda functions through GitLab CI/CD using the following Serverless frameworks: - -- [Serverless Framework with AWS](#serverless-framework) -- [AWS' Serverless Application Model (SAM)](#aws-serverless-application-model) - -## Serverless Framework - -The [Serverless Framework can deploy to AWS](https://www.serverless.com/framework/docs/providers/aws/). - -We have prepared an example with a step-by-step guide to create a simple function and deploy it on AWS. - -Additionally, in the [How To section](#how-to), you can read about different use cases like: - -- Running a function locally. -- Working with secrets. -- Setting up CORS. - -Alternatively, you can quickly [create a new project with a template](../../working_with_projects.md#create-a-project). The [`Serverless Framework/JS` template](https://gitlab.com/gitlab-org/project-templates/serverless-framework/) already includes all parts described below. - -### Example - -This example shows you how to: - -1. Create a basic AWS Lambda Node.js function. -1. Link the function to an API Gateway `GET` endpoint. - -#### Steps - -The example consists of the following steps: - -1. Creating a Lambda handler function. -1. Creating a `serverless.yml` file. -1. Crafting the `.gitlab-ci.yml` file. -1. Setting up your AWS credentials with your GitLab account. -1. Deploying your function. -1. Testing the deployed function. - -Lets take it step by step. - -#### Creating a Lambda handler function - -Your Lambda function is the primary handler of requests. In this case, create a very simple Node.js `hello` function: - -```javascript -'use strict'; - -module.exports.hello = async event => { - return { - statusCode: 200, - body: JSON.stringify( - { - message: 'Your function executed successfully!' - }, - null, - 2 - ), - }; -}; -``` - -Place this code in the file `src/handler.js`. - -`src` is the standard location for serverless functions, but is customizable should you desire that. - -In our case, `module.exports.hello` defines the `hello` handler to reference later in the `serverless.yml`. - -You can learn more about the [AWS Lambda Node.js function handler](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html) and all its various options in its documentation. - -#### Creating a `serverless.yml` file - -In the root of your project, create a `serverless.yml` file containing configuration specifics for the Serverless Framework. - -Put the following code in the file: - -```yaml -service: gitlab-example -provider: - name: aws - runtime: nodejs14.x - -functions: - hello: - handler: src/handler.hello - events: - - http: GET hello -``` - -Our function contains a handler and a event. - -The handler definition provisions the Lambda function using the source code located `src/handler.hello`. - -The `events` declaration creates an AWS API Gateway `GET` endpoint to receive external requests and hand them over to the Lambda function via a service integration. - -You can read more about the [available properties and additional configuration possibilities](https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml/) of the Serverless Framework. - -#### Crafting the `.gitlab-ci.yml` file - -In a `.gitlab-ci.yml` file in the root of your project, place the following code: - -```yaml -image: node:latest - -stages: - - deploy - -production: - stage: deploy - before_script: - - npm config set prefix /usr/local - - npm install -g serverless - script: - - serverless deploy --stage production --verbose - environment: production -``` - -This example code does the following: - -1. Uses the `node:latest` image for all GitLab CI/CD builds -1. The `deploy` stage: - - Installs the Serverless Framework. - - Deploys the serverless function to your AWS account using the AWS credentials - defined above. - -#### Setting up your AWS credentials with your GitLab account - -In order to interact with your AWS account, the GitLab CI/CD pipelines require both `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` to be defined in your GitLab settings under **Settings > CI/CD > Variables**. -For more information please see [Create a custom variable in the UI](../../../../ci/variables/index.md#custom-variables-validated-by-gitlab). - - The AWS credentials you provide must include IAM policies that provision correct - access control to AWS Lambda, API Gateway, CloudFormation, and IAM resources. - -#### Deploying your function - -`git push` the changes to your GitLab repository and the GitLab build pipeline deploys your function. - -Your GitLab deploy stage log contains output containing your AWS Lambda endpoint URL, -with log lines similar to this: - -```plaintext -endpoints: - GET - https://u768nzby1j.execute-api.us-east-1.amazonaws.com/production/hello -``` - -#### Manually testing your function - -Running the following `curl` command should trigger your function. -Your URL should be the one retrieved from the GitLab deploy stage log: - -```shell -curl "https://u768nzby1j.execute-api.us-east-1.amazonaws.com/production/hello" -``` - -That should output: - -```json -{ - "message": "Your function executed successfully!" -} -``` - -Hooray! You now have a AWS Lambda function deployed via GitLab CI/CD. - -Nice work! - -### How To - -In this section, we show you how to build on the basic example to: - -- Run the function locally. -- Set up secret variables. -- Set up CORS. - -#### Running function locally - -The `serverless-offline` plugin allows to run your code locally. To run your code locally: - -1. Add the following to your `serverless.yml`: - - ```yaml - plugins: - - serverless-offline - ``` - -1. Start the service by running the following command: - - ```shell - serverless offline - ``` - -Running the following `curl` command should trigger your function. - -```shell -curl "http://localhost:3000/hello" -``` - -It should output: - -```json -{ - "message": "Your function executed successfully!" -} -``` - -#### Secret variables - -Secrets are injected into your functions using environment variables. - -By defining variables in the provider section of the `serverless.yml`, you add them to -the environment of the deployed function: - -```yaml -provider: - # Other configuration omitted - # ... - environment: - A_VARIABLE: ${env:A_VARIABLE} -``` - -From there, you can reference them in your functions as well. -Remember to add `A_VARIABLE` to your GitLab CI/CD variables under **Settings > CI/CD > Variables** to be picked up and deployed with your function. - -NOTE: -Anyone with access to the AWS environment may be able to see the values of those -variables persisted in the lambda definition. - -#### Setting up CORS - -If you want to set up a web page that makes calls to your function, like we have done in the [template](https://gitlab.com/gitlab-org/project-templates/serverless-framework/), you need to deal with the Cross-Origin Resource Sharing (CORS). - -The quick way to do that is to add the `cors: true` flag to the HTTP endpoint in your `serverless.yml`: - -```yaml -functions: - hello: - handler: src/handler.hello - events: - - http: # Rewrite this part to enable CORS - path: hello - method: get - cors: true # <-- CORS here -``` - -You also need to return CORS specific headers in your function response: - -```javascript -'use strict'; - -module.exports.hello = async event => { - return { - statusCode: 200, - headers: { - // Uncomment the line below if you need access to cookies or authentication - // 'Access-Control-Allow-Credentials': true, - 'Access-Control-Allow-Origin': '*' - }, - body: JSON.stringify( - { - message: 'Your function executed successfully!' - }, - null, - 2 - ), - }; -}; -``` - -For more information, see the [Your CORS and API Gateway survival guide](https://www.serverless.com/blog/cors-api-gateway-survival-guide/) -blog post written by the Serverless Framework team. - -#### Writing automated tests - -The [Serverless Framework](https://gitlab.com/gitlab-org/project-templates/serverless-framework/) -example project shows how to use Jest, Axios, and `serverless-offline` plugin to do -automated testing of both local and deployed serverless function. - -### Examples and template - -The example code is available: - -- As a [clonable repository](https://gitlab.com/gitlab-org/serverless/examples/serverless-framework-js). -- In a version with [tests and secret variables](https://gitlab.com/gitlab-org/project-templates/serverless-framework/). - -You can also use a [template](../../working_with_projects.md#create-a-project) -(based on the version with tests and secret variables) from within the GitLab UI (see -the `Serverless Framework/JS` template). - -## AWS Serverless Application Model - -AWS Serverless Application Model is an open source framework for building serverless -applications. It makes it easier to build and deploy serverless applications. For more -details, please take a look at AWS documentation on [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/). - -### Deploying AWS Lambda function using AWS SAM and GitLab CI/CD - -GitLab allows developers to build and deploy serverless applications using the combination of: - -- [AWS Serverless Application Model (AWS SAM)](https://aws.amazon.com/serverless/sam/). -- GitLab CI/CD. - -### Example - -This example shows you how to: - -- Install SAM CLI. -- Create a sample SAM application including a Lambda function and API Gateway. -- Build and deploy the application to your AWS account using GitLab CI/CD. - -### Steps - -The example consists of the following steps: - -1. Installing SAM CLI. -1. Creating an AWS SAM application using SAM CLI. -1. Crafting the `.gitlab-ci.yml` file. -1. Setting up your AWS credentials with your GitLab account. -1. Deploying your application. -1. Testing the deployed function. - -### Installing SAM CLI - -AWS SAM provides a CLI called AWS SAM CLI to make it easier to create and manage -applications. - -Some steps in this documentation use SAM CLI. Follow the instructions for -[installing SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) -to install and configure SAM CLI. - -If you use [AWS Cloud9](https://aws.amazon.com/cloud9/) as your integrated development -environment (IDE), the following are installed for you: - -- [AWS Command Line Interface](https://docs.aws.amazon.com/en_pv/cli/latest/userguide/cli-chap-install.html) -- [SAM CLI](https://docs.aws.amazon.com/en_pv/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) -- [Docker](https://docs.docker.com/install/) and necessary Docker images. - -### Creating an AWS SAM application using SAM CLI - -To create a new AWS SAM application: - -1. Create a new GitLab project. -1. `git clone` the project into your local environment. -1. Change to the newly cloned project and create a new SAM app using the following command: - - ```shell - sam init -r python3.8 -n gitlabpoc --app-template "hello-world" - ``` - -1. `git push` the application back to the GitLab project. - -This creates a SAM app named `gitlabpoc` using the default configuration, a single -Python 3.8 function invoked by an [Amazon API Gateway](https://aws.amazon.com/api-gateway/) -endpoint. To see additional runtimes supported by SAM and options for `sam init`, run: - -```shell -sam init -h -``` - -### Setting up your AWS credentials with your GitLab account - -In order to interact with your AWS account, the GitLab CI/CD pipelines require both -`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` to be set in the project's CI/CD variables. - -To set these: - -1. Navigate to the project's **Settings > CI/CD**. -1. Expand the **Variables** section and create entries for `AWS_ACCESS_KEY_ID` and - `AWS_SECRET_ACCESS_KEY`. -1. Mask the credentials so they do not show in logs using the **Masked** toggle. - -The AWS credentials you provide must include IAM policies that provision correct access -control to AWS Lambda, API Gateway, CloudFormation, and IAM resources. - -### Crafting the `.gitlab-ci.yml` file - -In a [`.gitlab-ci.yml`](../../../../ci/yaml/index.md) file in the root of your project, -add the following and replace `` with the name of the S3 bucket where you -want to store your package: - -```yaml -image: python:latest - -stages: - - deploy - -production: - stage: deploy - before_script: - - apt-get update - - apt-get install -y python3-pip - - pip3 install awscli --upgrade - - pip3 install aws-sam-cli --upgrade - script: - - sam build - - sam package --output-template-file packaged.yaml --s3-bucket - - sam deploy --template-file packaged.yaml --stack-name gitlabpoc --s3-bucket --capabilities CAPABILITY_IAM --region us-east-1 - environment: production -``` - -Let's examine the configuration file more closely: - -- `image` specifies the Docker image to use for this build. This is the latest Python - image since the sample application is written in Python. -- AWS CLI and AWS SAM CLI are installed in the `before_script` section. -- SAM build, package, and deploy commands are used to build, package, and deploy the - application. - -### Deploying your application - -Push changes to your GitLab repository and the GitLab build pipeline -deploys your application. If your: - -- Build and deploy are successful, [test your deployed application](#testing-the-deployed-application). -- Build fails, look at the build log to see why the build failed. Some common reasons - the build might fail are: - - - Incompatible versions of software. For example, Python runtime version might be - different from the Python on the build machine. Address this by installing the - required versions of the software. - - You may not be able to access your AWS account from GitLab. Check the CI/CD variables - you set up with AWS credentials. - - You may not have permission to deploy a serverless application. Make sure you - provide all required permissions to deploy a serverless application. - -### Testing the deployed application - -To test the application you deployed, please go to the build log and follow the following steps: - -1. Click on "Show complete raw" on the upper right-hand corner: - - ![SAM complete raw](img/sam-complete-raw.png) - -1. Look for HelloWorldApi – API Gateway endpoint similar to shown below: - - ![SAM API endpoint](img/sam-api-endpoint.png) - -1. Use curl to test the API. For example: - - ```shell - curl "https://py4rg7qtlg.execute-api.us-east-1.amazonaws.com/Prod/hello/" - ``` - -Output should be: - -```json -{"message": "hello world"} -``` - -### Testing Locally - -AWS SAM provides functionality to test your applications locally. You must have AWS SAM -CLI installed locally for you to test locally. - -First, test the function. - -SAM provides a default event in `events/event.json` that includes a message body of: - -```plaintext -{\"message\": \"hello world\"} -``` - -If you pass that event into the `HelloWorldFunction`, it should respond with the same -body. - -Invoke the function by running: - -```shell -sam local invoke HelloWorldFunction -e events/event.json -``` - -Output should be: - -```json -{"message": "hello world"} -``` - -After you confirm that Lambda function is working as expected, test the API Gateway -using following steps. - -Start the API locally by running: - -```shell -sam local start-api -``` - -SAM again launches a Docker container, this time with a mocked Amazon API Gateway -listening on `localhost:3000`. - -Call the `hello` API by running: - -```shell -curl "http://127.0.0.1:3000/hello" -``` - -Output again should be: - -```json -{"message": "hello world"} -``` +This feature was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/6) in GitLab 14.3 and [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86267) in GitLab 15.0. diff --git a/doc/user/project/clusters/serverless/img/function-details-loaded_v14_0.png b/doc/user/project/clusters/serverless/img/function-details-loaded_v14_0.png deleted file mode 100644 index a19d236fc395af9a6599ae185f10ee029bc506eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21864 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 diff --git a/doc/user/project/clusters/serverless/img/function-endpoint.png b/doc/user/project/clusters/serverless/img/function-endpoint.png deleted file mode 100644 index a38fe2cb6c2020a6c1b1fe0e78bb27bb009f3b3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14641 zcmcJ$bx>Sivo<*Z)=WFGyYh000aHd1(y*00jsDAVi}f zJ!v4y{q#>?$kq}n5>K-P^gA<@r#XU~hMXjza+GWb06;{s)zWjUqJyQo8I`vK$k9#ETZHDXh2WF?N6kS)_1DDRUW7(ZMV(5@$<>mI zhy5-48ye9UR8&;Lt`=5;8q%`=a(|kM(Ac=UI}37fczJoTdvUWnxmt5@3J3^ryy4>D z;$nNUU~}_vbT{*6b9AHqXOaI{N7~ZO+|}0E-PXyG>d(4nKqn7(5gM95f&TOJ&ps`^ zZT~BhquakO>*)eH{vaHj>~A>!CpULntN+FA5Au)O-|PA(obaE;1l4W5EkXLywhoq# zZckeiA zf7uIj{2}V!i2Elif3;6(5q%-d@t-LceKBY`DD;%|=g#u_ZU6w`Q{@oSBPeLl9Y0kG zjk>ay%+1Zs=H@0G4!^y-J3cyoczC$Kzkhsu+}PM)V`DoyI+~lCTU%Rmc6J^b8tUli zsH>|hDk`d|s3$|(VUtV77>FI&N;Dm$(S6A1{s>-geuG!gHA0MCN<6|c$r;CdVK|#UO(^FMd z)$s6eZEfw<)z#hI-L0*y?d@$96_vxILoF?>mX?;iy}el&%+k_wad9yvC8fW=zq7M* zczC#~s%mCt1`36K{P?lGy?tqE>G$v7-@bj@+TLnxY?POm|F!z7y1Lrm-+yOkr>v~3 zwXOB!IR#qb;Bfh@AwzjtE>FE#% zBqJlk)6=uCurMGXU~Ft`Wo6~dmoK5Aq51jwW@ctTe*7317|_trn4Fx9kB{%|?KLto z%FWFU2??2=o{o!)Yieo&0)bgsSwTTT1qB6(iHYUq<)1!%^6>C5G&D?0OUusAc5!j} z`t@r=LqkkVOk`vv2n33XiV6%2{QUW|y}iAGfq}4yaMs=^9RNTDP>`0;@}A#cI&i}G zKZ~vo(4L?Uk~yi%wUw(0APXXCPEYA#c0DKF3i7q@kN&ibLyGC1t~5mN<)27 zt=|6Op$SSCqBH^k^&ZV!?k)* zvnj~@0l_G*$ozfZw@u*jz*C@`rxiWf-HDYKsC~T@$nPb3doe4I1U0sq8+w1}&GsO& z<|A2)URi;*5(mo&qfqoLZdW>-AEqIdC)uN}9oeu$--{DGlvulHTJP?MB9=wn{HtjofS7~vp`@cDu{UsW%Va1bYfY-C(PYPC z*9RPr5k<;yT_%xOhYcLd1o)v(LP6c@yx`c5To^^#Puy!fZ69U}+sHh-sdv|KE%KPq5Xqc~61UU?_XkVaKuJTplW&=Xf$>_CNrx&AliHK1Fw zV1*&1opW(#8wqy-+EbkeaWS(#e0Vjax!UV2D7*@LQZEkpny+Or9*I`lcb8O|HPP-H zA3T}+ZS{^;RZCQgy&MZOvvne-@Hj_^YBx9E(uYwIV{vSmVl51D2gt}#eu4?{crJ%w zgvRfKQ9Jwr^7~n&LXX|OhC$|=l<~q;5V0j#Qap7RE*-C9c{d`f3h;we2DwBqz2bio z{6BH>U;MNDLpT8UKZO4WjQ;VyQ~?Gd90xsBc%Wkx%A0rpJt4=PpI{L*m{k9YulP@< zf}#H}gr{0Kc2nHHIyPh+^;+DEr=&=4HZZzbbKAY@SexBE3Cd1=<_tp^1`+?n9<1J8*P16__1o!Z z%ACq;dfv-!;NAUGA#~#kWg~m8?fgKWUl=8y4kk#k#TOcbKFf#$h9oP2xNd|yf3_bi z>X8B{v#)NaxQ2U1`5ihZsk*o{H z33&uvgbYsGEG;Ll&KLk&g(@A>vmaJA~^5!h$7A)@0Pb#v<-HPHSX(L`N zTTnrUl3acI?W9+ozx&pKkPPr!`mOZLfOe8>Cz5z z7voFQX_1pZ=4cNZ9SgnGk{EP!IV^LIbahkNjR$cTymC*ThidYHm0s2l&n;$5>uN73 zHxN=ml(JxK^@kvvE3^)_7M{;)j0X0YJuY`r_YXOSNgEo5lVOdrLq_&`Tdn0$9;hGo z1&A|kYVc{%!u$Z5!A+8o2aeb?a`ZfjfA5+4gB zV>c>G^N{M|t(kWS1Y(ax` zhF4xUX!k}eDLS=w(#6b{;rgWbt{e)NL)`@W?(MDjtvldm#4u8J7gUjt@FL0YPcSyr zyQyX7h5)^sTcLrzyiHn)LCK=W&e-^l7Mjn)yper2L0;-g`+X-gD-`IV@O{a-x~L$G zP>veZ`QLFpXLUKUeC%WMd04;0IwBqEt{=G2!hh$ao=FTzw;ANMG#ef3d_COYRtPN^ zKhZU7k)DJyv|if5k#z@`t)KGcDkC9MK7yCwpYcob2ApNa`wE*Zfl*J5$D~eM1r#I< zBuA@>c_>J3v17p~%808nL(}cssvO+7`l554mzA~+xr)KQABb=NRGy-I!f;z%sYPlp zhqT?lcOvRuSW95qi?u)6A^EP{v5HMs4J0i1P)X}EQPUj?U1p3@5gllp#>-geMr&|d z>0q}>fz{Vz{Z1%yt1_Mzn6!9rpaR05kh-0r&zRT0g>^D!HE#!aJ-s%SaipQj<7Obg z-Hy!WUKo<}a4UKXDfat~`nssDqrTSp1U!J7F=D!S^-v#uNi4q~kOZs1=$uc9CnX(L z0ErAKq}JBj?9j|vC9PW96|&g~ht!+RylSwyGQc{|ydU}9OfSMWtA6dEf=l{xmRIlq zL?pj|{}nbaka|C+0OHyORXH@<0*4a9F=nHAVu5^x;8|nlqs;pw#$i=ZWsoXRE`y%; zDgfquk3}3qFBpfwe&n7q7vt$5+jXQ{TQ*QRZAxHoFT? z61bli&C)Lh;*$n>0i8}OJ{FFRKLlP#H+;|N35Px_4rjcn0h9K&kBI|ysUYogzYPr? zlVF7@KTeRs@-7*cZ3empXZSxFWf-p{KOnJAD*>$Nc{SKyUh-VCgW7J2<1i9xuYGH} zFD@%P&xRMF43mfhd+{9cXq{;nHw{atbbIhjbZZ{cPp}O|5dW94%0Rmmn54gG>5*Q_ z7=PhK>FjRzn2uN!qb4K(yvYFX!kT5AfpTp3Fhm6~ANrrlz~H*Fya$-41F7T>L(6lI zyos4E!m1WWs^SXsXY(}iP8@W0%Ot?NlzuMi5L!|5P$M)dLd+^ zoRFzfFtFdSU5N7`g#aL}A7E{DGxb{nem|zk8v=6UUOJ}q5F^Imi}bwVagH)IU9v!b zIN+nHd3PUra%d!N(Iu;!5M}&)ZSu~+81+uEF5$Xj#^XdWRlD~>iYV(eK5Bg;X5CQ8 z)Yi{w7)`o7@hb6yA2F|2h9~WHvD`KQvYk|}g(TKM-wwo`0_J~u7@0(D=A64?!2;1N&$cP9)FWLR% zwaK5@RM=?P3*O^i6YSqJO#jl1`bC22B7&(7>2G#}?e6b?qW!T#M||3c9yS{CO)w4^ zodh2l@ri;b067vRGkh6XBoL(S;TeNX;``L{fdloA*8>hL zXcs)YK`%+uzs~Do&e-EfLYrkRG?k!fy0Ii*sL<-xvUjUSJ4GIytjh;<>-U(LENv4Rz>?|o{yS+pZ00uFSpF?thU2&{Y4TIRf)`lhTYBRAs?-e! zRK66;I+*c9w}CdPH_^vHiQG#`IMwE!Tn-X2fI!g1$aLE!0i3n*8~pA$WLl%Sb{Bcp zDthDhX?~_;o$I*dPPFZB*KpUZo`Dyy_};J-$et@UH>G>!_r+JF3aL#9_6(zZY_V1$ z(*v*PJ9@iNcG4a+KmU=v%bE%{D6RLG|30`e>hq9AznSE7;p^ie3WU0Tn<|M}XVQ92 zWDRP5HRB{dGGWM?`q^U9Bi0&sKq0~tUbE=|w2zfun?)sP>1FcAu z9+j2TY17 zhN#^)2*G==AnHgtw-v>on_$XKFRX1s`RDs-eeDFhkNU)8jt-DQ;K-1j@1d(Q<&M0( ze2!ZAY8{bCd&GyZ&vL9Xr#uGZI~+#7l-LZ7$gt<&vQJ< zAI*p$S5fc#1?H4;u*vQ}G^~(rHAzW=KKAh$Jj-%cJaN3d!7%hWRX^qI@XT+2F)Exw z5aE0QL7<`%z7pCs53c}Dk;KSF93NmL<4`Qc=$cd5GY*6laz)Gyj`J9=`c zYBX|^oRQOxG$6nrmW*W!K?7vol)W zQ{=p&fd}bVsh4j>DSC?3)OyS*&w3<@A=_K2CoqTjN}jvsv_|yHPi-c(I$!(q8^a{O zAIt4;&ZYQ=pIOBhlG$IcBQm(Q;)pyVYSPMXy7Rw|dYmNy)4T91p5nIk(BCAcQUM^G z!|6aU&Mn%wPh;?~0@94jilO3XR# z%fEz&i2E^PKqzW`rFS(nCrlnMfrRrmZj~CeK5;4TH(GM`cSp8$6#{T$ z*?ly2OYr%ISJO=06j2(++ODMyOQ!fX4tOckTR0WGRql5twa;x3R($@odWS)zcNvuU zQaLh*i?EYC!n9}=R-1u2iTg4d_azD~ufk9S$E_D{JMow6au~UUktGI1Cz}sx=Y?B0 zi+{g(LXbY|ZbG~vE23j@4o#a@hFN<8#+hKO)2bo=-_+*OmQv;p3k#3SZL;PW@jc2zEI!-g-hF+NsCaF z8q3K28KFnM21xx?0c4EfTXv*4uh&wt3)51bLs+ z7oP3le$>ZJg|>;a?}f{;OvC46nON6fDWI59E5^z;6EFQ)?_Xi;Y>u&pyTro@$i4&d zLQe?jsb~5WRo(R!8WBY!wOMw~wNW82*S)4^CLEtOtsFT$(mQQDZ!BQ&o(P+kVB*5$H0)>UMOxH9b z$W{|l$$Sf|1Pr7cH~Jmo(s1C>8@`Va|nB z<#jYIh_1vS=Y}c;XrAtl@KVctXR}DpS?Tn6 zd?@RYjxyh8$GWD;7%h<|J4q=dS-==_R=*pGw8f*g;67woP~D<9n#$ z>R3q(gH#6xEQUS-W^s2rKP~@$bA6U;XWO9sZsUchjm z;#iPZ*{-3c7}PHLoW`>s6>{_Rm`O)_D%$VstU=T*EIWO(+wur-c|!cQ!9-zMmLfZ` z=LQiA<&gp6%08<0+=tHuBMCYt_C0Y}0z*Tw_ukGMKd`p(KuI{R)_n!FIL?L)CCZVd z(xn$K?C!CJc6Q9+wiI7>Rz#qp06bJ4JfMh`d7Tn@%K4hY;k90Y28{vTl1-7Ea;Hbr zNI2~3h2n_XSEHD97en$zXT7d@d6Lu)aFYrbuyvOj-tiy~9$!l*4UD^q_#SyMapJu* zX=owPr$NJXX6^WU=>l75j~D5|m*IE(&MBSs^8uOKv<5ZGTB*JrMOKd%d-=x=Uq0yW zp9C;G{a8p5TIW$#cDyxT#68(5@Era;yW~x?Bg--p_-iONog7lAVo}V+-fq_gmjF!| z(+CssFZD!_+%fa*k0Q@CU;!htBEtKrat>cemVZw8shp0vglsjMb^Z=a0X*>_ZF`0J z6CG9k^*U9mvKRSF@5I!JK08ZG4HXI2pc&4U=l{k$W{IVuT8X>26Hq_kc^Sokd7Wcf z!Hnl}apO>AkLrel=r%vsE1vy=Q0GL(b5uzwR=6u$gRUEE#mln6NK4wEt7HDmjp09K zRl2}K$HVKwvOwfp>U^>nvgM!E{Mcu|eV%7Livc!AoM4ni=+$I9>Bm+l6P@R)###H`zZJ0 zZOGg8@mQ&JRC@j_UX={@&%6q0yh)TdSa7(@#L^C(evO6Uae!EGz2HHmqgUrb-;&~k zi~5y;>fnyTPA^RLp6UA}oUJ6xP=Dn!a=;2r?(D7uJlj+htxwfN;Fe}R)Z`dIW4jj9xQfu>tXiHY|$R7C$|$}OSF7CpTmQlK zjm|bRU@nf}GP~ub?3%=+XRxaA=-0$oBZG9Cvv;73SY|I-nP1id7yhv3J+*_BvFO2= zRR2%A*DThj@K1cg#$?8y_|W>TDHWM#GDa76MN9LF3~wm>gL-yRZ#)lK=RHTjur8VF zm-7d+7WmmFa1)_>7U9LaQqmQBVH8f=_IWy^O@;1BQ zRx=gB)XFFQ-OD>a1KWF0t3&Yj9C)$r)#lgW(}r-@R3G;xk2RHX37NQ=-QMW!ZpqR1 zDTo=t!hON5*&R&g_M)+(jL(wHvT7}Blmos5H}V}Wmv6nFYU{}AI1aCfV(~=2`JOOS z;^P=JoO$s9e!DeM32877m|!Fqh-Lz118G=P?`+RXIVzLY%M~g`I$OV^?=D8j4iDKQ z2@W>j)fD^wUM*CDBqrW@IfFtr)c2(*GcF4Zhbd69Ij-yORq{6o_Si&&J?M7zS1)-p za@!lb%soX?)ckx89oPi>7i?M|(^kIHPBfelEX=^e=CxL<{6?g@HvASEJGj_1beMrz-dHFdG@{&}MGQm`c;Rf?!5qr>#{oGk!D ztc=0(jMBJ9ZNkIv^`fRfV6RW)h_`Rqin~Oxt~Tj|=Lh?MWyiGm@ZTaOCF2i9d{yQ= z4)5A23|+@CtTJzrJ*~OZT#fUycVO;wTdU@il~>Wv)vUv_bSuT70p#e`H{2nE*YIyi zgG~h$o4$s_EOTk)+&AhBy!)b5NdxO+QeIu$8oOf_gAd1&{ez%6-~lFz-D}ZcjE{|T zh2ghZu0Go-Fh1RL>M;q?I8Ru9!wLXq)9LFRgwuS5K52MY(7%$Rt!5xrxnW`xjGElY zvW%~ODH{VMja+QC@~>%SqvK<1GrZg$qsSl_HJ$_H8ew^DtL6V*Ch>5pV`@ljE7??= zR@6pYoF6>wp`vR!YV<5c_hyJ>lLGWkB^R0gD5H ztzge6a-&8nAon@G{Z3i?4t_=v9y(wo4<_zhvV$)~U{Qn)NSB0ETtwQ&5s<#rNCxCG z<1d6ian_Ts_jHX+gio|aKk*$5eD{Y${Fr~BCpP~t=qVHb3E8~vp%@7GnRi9isAbC8 z(AMgH9cNT&junj(jH(IAa3#ld&Dbr5k%rkEdG%1}lL^$B&PU9Mmxt2Jv0#Iqn36mLAWmET3%~; z89$r->;3#*=>t-}n@49g&V%oD#3<)h*xCe^m^&fzA*{Fzv;EqPEE+J;arks-5 zTh*_coD87sdN)r$lMbV|$s3{0&fR>P#df9oqHJ4xB=*YM)=e$0Y(=%SR?+9mbm%ZHEh%STIy+DNi;&n1GNis=uRd2I2z>Nkx&> zbzP=|eJmAEA)9;3T~MUo7|;31|4&mIaK zU}3RE*YA_UrkZs{jJ)~wX=p1%r}H*m9jg1nLXJ`YK)Y2%FlsX(|^Ls zCPT!S#IBt?nLXLbb4pbqhLb1o7brw6%uuBwB%L?!UtoM&W0$+^Go0XN?}2MKE%()) zlneFW8zTjroV#C(>iP+jdvUPy*W?i_q{fdWtJVMBZUXWmmX=<*H%oJ!xOXZ+@=CqK zK;`cZdXmbtPjyt$i)lS`^MTwu<*oQJL(>&7-JC?X0?eL3egeUqvC`yx?-7dqvG8+f z!LEWKk;4g#aqW4s5kaxSolMRL9r@Ke*aQ-LQ>dN7SAtFm)+k?xgZXaf@Z-x+T0 zU5}~BP6wY9@99X2tSKZ{$qxvF!2kfmX3T-5`*gWKFxh^~W~o7$&LLw%-t6dP0)(lx}V-Lf{;FH?fESMcp+ z%18h~!T^oDzk&0y)qMz`q-R;r#;z5p)>Aq#|JU{>1IGFyh)1W0=c@^@Zk|uTM#FM- zORGp3pS`HRf>2|>`;7B$Kq(#1Uh6LkyDg&Q}S|BZ$tiSr6lPy~apI3(bVHJQfCRU`uc2O8 zuTZ_OY3$?EUg5!B6*Nt*p-$!7RKa<9%~;dy%Tr3;X?MGiq8UQ;<}Tk)AL2ycw8%R!RXG;&=y29W1P zabGf(Z#bsa7ZMIb-o?HAqL3lnLoDi$u86a2t_4I-SBMzsbDJt!^>7wET$9J~)jLpk zFbd;p{4FF4Itj537mZAb(;EN}p^^4*R*AF%ApI}XT4LXA=FNEhmacl`i}%`pgIx>`CVr-x_2)#8HOhE-%Vk8)BcM3AbKw z&)5Ng<9KUIEAQ5KH7@K|^laaI8&3C84 zUDFmn;E|041_@o)F8YwCKtJi_=wo$<6qvt(RfKC$*gE@@ICk8m)Bo(m1pxr&f2)mg z(QWVhqbVf@78|WMXk^jp^~v&)uHN`0k{c@cX4lJJAXWWj0L9-+%Qscuv;i5ywmrg= z$!CYx!rAU(dqHiV&g=Gb;n&hyAGyiovh!*QXETok`K>x{6r#-!uM7q++Y5CKSx9w0`UGdKs=f z9qaIV)v(+ar7z9FV#F^rkV4wDha%NZZh)!rqPkhC$E8Asqisn-531$@*Qt;xxEu`BS$s1N1X;CW^25&s-|o zl{P&KtKyT0TKQXe`n+Ie2g;Ov9RKA{j}>aBci=>`bmQDw{Uj2yYpn8T?gE(Ib?Rb; zuL}_?Ow_o(Ni@Ho{v5{d{Te+{-WjbgQcuC|PKjZLuOPPfEAifdNYraU@*Q8eYY6sX5eUck2Acz zpG#bcznFBXTNY;j44vFwAg+?u%V^6aE{}vP`5jjVPbr2*+BCs4=YNhOgjMQBP z&k=^$IoRXOc(TM0dBhqpcP@M2jN3X&QS)uS_of@^lCWaKK9h;>j72)<81zka8 zAC4D&`3sUXH^RgVK^h$;f`w7-#qc0JY#L}m_c|W93|w~gt#CJA|Z738-=+3jN|7iGb?Z-DnEWtTlA;_Bq03#y+uwad?H*N z#NYkUfxQ6Tyep`VEb?Ke6@7RcDX|{lgiOQRLqj;FSyKG*mPQALn8aXd;~?@P={z;* z)}}idUlIx22GqGDH>l92l{%oiqV!&|(Wuo(YwMr^Cfi-9l)kMDW1#!dj!?Hqp|Bgi zT=yxGpa?OfF6^Tiu$_}4@4he=jL*5o$sGjUgj~Ap`)%Iq1Y@d;v842 zDK*$E+p8Y=XTFBBCA>ik4$|eHge6W&v_BBZ=octpgy@T6&hf`&*?qwOsAsuZvX)MWW8drPTsB=Swg0x! ztawheYAmvz2A)d}`Gr1wwfqT3=v5>Pi82J{ll{4}p?-Bm5>cmcHP;*CSC}D$U8+h@ zlgA%hsgU9@=|x-yHdyP&+oBpm=xA;I>FjG@@nuc4b^MMBH&ufaVZOpW6I5q{zLl$z zhZ$l}YQqGTwX2deZ5QBH!$F^UUH@!nTkFTonkhI!8>7gOM4KT>| zGBl!$_xo+rg@Ko`ap02mYi8fhI9j(e_Y9KPr2+3h#u?o6~( z+0ejG`m&~torx3`*FgZ(I6yHXqrpx`hMN;i6&bPK6l+vj@veMA#}zvCO{2avLauxF z2Q8clnopGp>!Il;SxdJ25(iT_EU4g^B^qjI_vQCbe$az<404I>v&(@b0^}9v^YfBB zeZO981p#(yGgC94(5Xofi=q8hTn0J)&z3}3NhxuG2{e-i;ZxuW! zm=p}#%W$T_hT8dM-@`L+hso`vSqp8)cz5w23z%^}R$pR&O3SbSSupjmTT6Pxrv4@; zT&`RlBNkSU0ZGU1e@FvPl2U}_`sN8kU6pg(SR+wVM5b`S;a$i8ty5y_^c!I*8c&T^ zdAvx##CnzACo%3JSg-6S8l!i?ZXc~2Hg;^EeNq|BvC#?Fkpo4$zY%cLnPzyneb;CG zy0n4Lz_IO)`-lIUS9tJLAC0HJa)*dM1@Lm8nPMzZc0ULOqD?R;wTzLOm|YyTt%fX@ z9P#q&$!Fbnt$AhhBqJTBUZ6;np7wb9^<@t~JeN+5vQO?9a)DBw8kK`e;;9S1$FeBg z5yKHbzH5PK9^xu9UTN~Yh1zL+z_Bk0?<4@D>(esIt$sr9IKf?_ih6&dbem*`jjoXP z^9z)k6bLzy)6i&Dt@G9|o1V`IJwQXC^=nDg;U~C8W6@dkoDM+tLlmvXXOV}Ynr>oi z_DjE3&_$uMcK3+w7Y*yiXHRe1s;RL{I?@$id6iVmPW&( z^r+N4l4&f#^=4#$(lxgZ^o0mntlop*tGet??AY3|$osm;u!;poH|s6+QFo*xu)AM= zMt&AOZG)WgH#qT31uoDm=DxdVnv(oh>!b8pb9RfHJ-aStf9&0@=T`vZ&+zfLXu_dBCuLZN~)ZJhZquWhfJk| zHxV|55XE~|B{9>^4HKym+`jo_;!5-**l~o_gI@>wJD_#4v}H=ksiu1XP$eY97ddiA zL)dysvH6zd!vp`TJSTLIk;M_RSlA-6z^}f6i6qzj%YIw#ifvR;aiKkBC#@<8q`oV= z1)nK^WPhk%mrITcTL>3UD_O-VQF_(+TKkPf?MqO??as_D!jYa+HSwbV8z98eY)}p1 zCr2R{-+WKifF`pbMU09H)d1h8@a0vj&{TV*453Vgsqgl+`C^9U;qyO2B711h&!@;O z{Q371p9t*~XN`Imc_e%yJA#ls_%@S>(6gJ`_o0}WBtXMd@bn#%L9_(jHP+4@fiS6c z*g4@24CAh!Sy!~r#2)PB96`#%V(QviAg^7vN}ZCGpRb4^Tb@Vq0yvFl;7%=bLHt$O zFLIx9=Vz;d?kL-25iYvQOk9aKe)Jd8a7z8H7F?dYQ43oZ3P zC%o|)i98G%I=%hh1@f4r}SI1pW=YLpxkK~ z!1;*gP7(+9`B5@ThNu&u-*avrH<9XFKTYkFr%Nn`L|ZPzQ|&q)CYP7)3q76z$YjYS z>lf1F#P#ggLtByPl?vWu@Ot1bG|z!t=o8Z`i5$}34%vy-^s?5E9Ht(W>9!iAfPyyF zyC`0SQh91*Zw(S}tU2HL@NaDd@JXQ@9KYeR?j}9IYu}t5OV21ePz}BBY!^H9ciEJ^ zmrGjV<8pgEPO4Fp%(1KzNxg3}W)-pZ{|df0p-oKKBY)Vsk%+6j^FDb`XR~UuO<$5A zh_(6K#Z)BhtiXT!w3$%EDG5L!owyBL(^H1x!Torbx?h~TMGJEma(XCyj<3B^`#`w zT3i3+@@=0MkXoJ+o;KEg>0s({FDqx-i8%^SkMb5FA67l*Ha*n;9A(sGt2_9~Vg2(AGfm#rIAgxaz3B zw7te#%VCNNZlsmco`1RQTS>(G!9I`PHfhBKgDdYK&B(Nsq&&qSM;?0^!^{1pNeJ9s|>=3u(8Vuf<`{7jMHR`!y!sOD}`KE!b4(8U}wQ;cAgUtB%6d!U(h0lM|4!!gmoYuBj-xH8a?y#H zQRuCwj|LD`iFIj(f2E}A?xeG7A5Bgb1hT!Ps%c&-Lg;Q1>*m$k**N_=TsPf0#-X$i zX)s+&KX&3pDM*49=xn}SnkZptaAv+#8gvx6dbfWXr!TeujvKzLM|s-)j+Ho&Fzj}h;(piT=X~Wf4fuY z2%lP2{=Hv?{!h=!ALxG(dKn>&W^k32;e>yq-*LIBmXes#9TM%$%b(!SG zVi|?PkC4;h+fu7hsk%;2_#S3*f4?+xfSS?cuLDE~-{0}tauf-I=#1J}vr1=0*p QU2Omb8CB^@$@iiE2X@>r!T>SNrzTte$`HD^y5ZI0c83hpn@b zJBO_k{lA3#j~odzClg0YduK~K+n3LBjg0MFoJHv9o)!J)^DjHi+%5lCldaRgrv;xN z*E5EToAVXd|BcPr()|C2?HThgwtwdJFLlDt!UUBq-Oa4EB`j^sY@OhyiE{Jv2nhe9 z=l?+cuciOR3jQxvKAzYA&H7)c|6+Z%MNrAn(hTmVXJ?3V3v>M+uz%AFb3J?NzdZM^ zu>1ps$3+xFnCm|=7R5+=%I|=O9o=3=+X(;wGCm&&vZ@Tn000s|SzcZ8>FEhkCs#v5 zvJ6l>>nxCJ4a&i(H z8Y&?vF+MgvH8pj2cek{(grJ^setv#?dwYF-E%f7lZg%$cPxV$(!J9Bn+wzaj5iH;HR`Q_l?@bl--uCA`i%F4pR!kO9G`1ts$s;b7uMompk zPfyP;U%psbS*`wAU0z-;!pE1Eme$hJ8XFti+S=07*Sk2s*xTDXJUXv3=DL1bVLdp784VLmku8vpNfi#Z^)CVsAzL@^TfnN zKtMoGPtU7_dw+lbyu7@$^fUxX|E#PmzJx0wAt5B^nU;1EKg;{-k?& z*qcAfo!#Ho@BKkih$pz5w6#`SYg)C6WKeuCHkLDdT_+$A*}JuS{=nl}IktI=sF}WcaC^DEJ(r%& z8F{?Du{pDI^>t-r?&3(+zvlC77pdP8%*<@1wA5pvug})*Q|X;;<(*-Cr+Y?MVf)P1 z^+nF+9A)Ad=-Yj8@yOZgpXo$sW#3ZA_}b(BJ*8EtO5q}+cGmIS+|cn(OzYwO)z#zU z)AIHSU&b_!aei&XlWX(Z-0U2;Ws0=xPtV$u_}ZzZy!@NvwqARyfuBWG_ zNk~ZI%X)85Pg|N>=ncMY9Gu1fctUcX&MA7jJ~+(J%P*bQiUI&$0%Rq`)ZORzm+aIC zXApZ&(t=)*e2`&5C(J|q+(}9rhlYW4YPdQMaVQF4R8I}d#Bca4Eln}c#-m03Rx?lV z%AdCt!9a2ahbwF1$Kl&M<`jEBZ2p4oqb;!%>yg8BC-G4(i&3Vj*UxM4e}C+I(E#s* zarzSAK-vvL#~@A*ar4Q}9wi2KZTd+UKa|$BT*R3^?WrO%b&a z#7OKS01DnS5VR54d~``-iclI&{Iu?n_AUt%0if)!H)41tKrj^#+e)oKRvWt&A5>K0 z1ptV5=PX$#rPaiG6E+M1$`g@tdHip8lv0e2mluP(~co%9K?e{h&eE~RTwN$ zvi&WtoL$BT0I-(go7|v~{FT5Y_w@yo*DFwR{i8`Mp52SHCmaBvAedq;_=|~l)jlD} zv+PSB30)2bV0G0xjOdi+W1J)vh)3)G-MA6Juj_uBO95zBLE11bXNLg5nbA!K?LtmG z&mNo3i2?0VO^?kpUf_z?Kyk4~xCE(J>YqT0+6f(N53!>jJFEadq2<(Hq^|ZAMB*xf za012G>H;7z`2&X@IuhU)k@X2ld2nGMW5(Xq82qX1_T`~r?X-mlEIJts-C+j!=?-Q# z;-aC+8F_xSMFjk+<<)-)cNz^pxHT&U0I>CTc~-6S0toQCgIMAleiLQ^-y>rJe#Svg z2tfcq01DuJ1px2@_@4m)(1G~R@hk^U=?9_xp8)~@5A+Y@dGHVK|MJ6G#QzcWA6_{B z&&Dr?Y;eiZ?COYbvZ_MV$vNTtzxos3`!rk7s?bl*u(P)O=1nhWc(%|UpZ#P^;s9*; z)H1Fw_4hD77=qyUA&?PIiNSN%BJj;bx;@>WR(JclR_1T^8PAqbisHmDoxXX4fT>9U z5*Otrw@>*8EQmA1==r%w?9buHK*>GXNffyBg6W{#cPfUu*hRwNUBU;WB(>)`<4}AJ z6bYN-5&@eNiu`$=`V?u%1w>m0*{V=d8XXETrj@AxymtxMc+q8lfD!?fLGFSp#sg_! zL2Z*F{?XR;N-w2@;5w(n(M)*k{vv<7V*UIkgHH5O4er}X^$KCDrPCKz`DJMPQ3pxR z`2!K05v57f`jpNRM8fNej&{AKQmO6-Mi!^vS4hhXMm9#bxnK%U`xlD)wL^Ufc%TfA z3gP@kBQ`D%sKH#o*4F!EMJ5D{kIYAl3^vG$rnz(pa;EE!I*G*Ss zm|Jf9KV~{q=<$b~OE$Ptds~!LCnUa2hkP@t`uvzW`o?2c6YlH|$ML{iBUME$)Y2`I zb->epXh&SQTQRDlVSJH&iu*(I4Z{~DVXyW5zVgI}Z}L>L=>`|&0_V3x9a$`1g48T4 zM9y`Ehy%&zsOk?+Ix5b5tB0?{$&ug_Ih75}ooXnelWJA)s?2thsxPY@8tQ|^zIo8z zLg=Uxu43#oGPC^EX1aaC7yWc!BQc21IQl$^N zo3)Vo@oTE{W&7yOZfJ5xblV0z})G4A&U!n-~xm6tqrddqk*LSN|D z47KC}%-8vk$s$FNzPD<=$^sC)6bZ4E8 z*VbB!dH_WvphB2#&-NQN=?ZlM&_vaOk7lc;Y=wIpdqYcc7qtKvaqlLk+1>r9`ygja zD72-*VFh0m2_`0Bx~TiARiil&^fKHW=1g~0`uG0B5)2aNafuv}Rbtnt{p2<5H~@99 z)pcIT56`wrRg|Do?wHUd#eJdBePXQ_-M=CI=$T`Pt`=KkhCbwu+j}FBM z$W@B|7>I^4c;>8wV?O6BNVUKPZd@<$wsbfSY4*5j9VHjofN`JQC7GG5_?9{dRlj~} zGTB(KJR$S3?f>aU`}-AWnC#u_-}~Dck#iz8jspaIAI&Fhs5iV! z&?2(nZX*TVh7uCGk-&7T)2FU`3vV)-d|Ny(W)3r$g~02i2V^2F;MXJ(R|;!RS9$lD z_~n#LzEx-=wP8Pji%^OQGwkL+*WE2Qy;ggwzwJ)rTSX($P`g>0$(^w}Q+87HeaP_V z3{>j6iiZ4z#bkV)^M@N9Jh}w0*K-F;qaiAZR25rTvSjog3Jds8j~_H;4HTLV%j7hq zXO;(%J(hl2J~)b=MJ(k_ong0H^zch=2$6QqR5r~LKWUT z!SYQAKWgPUNZ*EVb4oA0Q8OQRZK{Hh5iJ;y$0U`^MEOr#b7pY&tgs5*EtH&nR$MTy z+*Zi;Yev7(x&!~9=s8fsEghauCF3^W9&9j^mLd;|1!#tnog z6+X>p&DNOS2Zt$eYl##Id!Dz7T#cuP)ijg5UPyS5vbF+O-rgSSI3&k?WcP__#{o)4 zA*geKZz(exwuF`5NwqxrnBCnmpbd^XZofG~rDnxB9VQ+DHw&5+GTi)R0Ot)6?2M<3 z%4e>8gmQ1f_!^vl8#kwu#I)50=1wdgc>P_2=GFGGn@%%|oNc~R0I6C}>vo)`%?S#D z6U;WmujlQ{vmJX#1is$yregvRT7H3joXMJtH+3g3-t&L1>%Z3+8k`=O{CVaj5vX~l z@jqT1`nyw4JvvI3!CiY&ujT9RRIL>0YLOvaS;mYd{LRe|70o0j8VPi=Pr-@kr zyt%RZ_TdRNf^lx;>bDvJNE6r>^Tg|QRdDoW_Znr$$VsrO!C~wr33=RRrDoTrBFYbZ z*)!vLm*U-nO+~KZxiKkO^0|&I&|Puu;siG-x7&43;~T3yuKM~l80=cU-*sc`tH#P> zf{o`Xu9wBkq(oKENnww2uqJ5%Aq#6~2)`8}TYoU*WDUVJtWm-^j1>A(mRK5t5hI}~ z6)EC5AeZDUCwZ98Nzfn@rs_TT-1+UNe z(R_XGBMpS&jaynl#$>f$3q)l>OTjnYPEPkm;TuucmlfXEYYgLVZgQ7nO*%ip$zqZB zFSV3^&=~!xGkWy>wk$-MBLV@C9%lCYSEbh!Nk!WQZ-1)GB@j3lN1H)L<0Y>di6hV? zm3|cy)mrVQae)i|7y{bm{U~sk+--(GsC)O=w?iLTeB;^e__RprW=d)N?drB9ye$0S z*Qq#;b~@B$$eFRdM5LYJ%Qa2u4{EE_R_+f(KVHXJ57&iBYHLPdm(0}#ANT%_oYi~; z-YvmlOg^nSujQP=5=1f|jw494j<-3BP`pMOiIeatdBB z^~RYJUd+=kWRWG6s0{?);ZyWVUAOKKUXnZor7o^FGv$=!f1RPmsQw|jk%%duGO7k1 z4n@P|r_xGo9@PTM<$PMw?wzJI*8Y2h1Eh){Pu>^rucSI;$QsYvvbT5qbF?{Cn{ts5OagfmNk0i17u>>4CU* zLyiI~RF+PYMa?I%xbQDh0(}!$odXr%&IJryGE0xEaQXUxusQk*taJ9d+5Rh0p#k6i z1eM+1+m8qFS5WN)gNFB&5+11*HeYDdgdUj6^@v>uT?>DKsavbvf1=FxR%w#-2fY2V zqaP5Bo^35ty(4hJGJ0RTD979*D5Xt&BlefkyR8pr(?s{^14iZXg3m4b*co$4LW~`s zB(uI4JQbXQrWZ8R@3;J4QO10f)Y89N9GLeWn$dMOS9+4<#8eLt?q#~+>L(Jp!&=IE z^GT#}4l9^d2Mcg44+=ax`>;u(gwB0vU!D;=)U;qS2hNaj;TF^Sy0keKN+i4gy=Kax zz3sE1t#z{GL1M(0*C#JjR*B?Nqp*1j8@yCO(Nt%StqjFG3u&4?Qk8jL8}~71TSc#< zEh{VBUCdl1OVn_F7;*Bn;w3{yQo;(ld5m*m>wf`tVZOyq>usQ%9zH$|yof5$!aJVo zl`L3~LHL=uNx{^@aB{i#9QD90*j`1Y$+f-ttrOSeXt%O;ISbT2FC!A-C#dIDPD&Z+FUDxLf0Q5jPwqhD#2HkrMM?#YI+nOkr`^01wi79ApOcW@_8;j;R)&^JZlWene>7I9 zAqA}Jvw+0vhVr*zT07?bZyQh?3`1d8sX;h>=0Q(?@+C-Sd_>D<-1?+scRIZ-R$3cK z!61^svCl4**QGZ8)z;Djqg8(wd9Q>xEIUq}?) z(N%ia=Zj`u!$`Vj za*_OA`TA*UZnsxQ^N2-9Ih1!`qUR-{nk_Oo<&1gIIodq}6*rnED94HP?=(+~IakfPBXI z4zvhV zYV!+|Df%23%Z4!1BhYlicU-8QyIS8fP}t`57ELokH|>GO2mrunwfnNiF_kM zz_BGSlxLIpJ6?U@@XS#ORgpzC;jZ6s`M`eI^6Wi7x!P+a?7$Iw!}QOvTCJDQ23i2a z+r$bEBxVa|>`R{^1+tumv~LWUL*f5D`Q`uP;Ab%4*{y)ke-r?oBjE?%or3!reu4)D zetOpYZ^VDo|AYR2)Bl71Z+bY*bKv2ZXQlrZ=l>u1|10hv`PYu!<7tDn12Yp1mVZ}p z-*$X;AEBcjq6#&(X?St>bgOK{ur##NBsI9$cy!0+OXqnzHog-peAV7@tWHa1QvS|5 zfJ zHmXFY3+%NEFr%YF&cfpfxcyB?AxZKct+-t?PBR;wP`c1HJA!Wv1(VQ5y_;stg@Q1w z-zm;O=^n{^=EuXr@T1u>L$ycK4X!!&)pFxSU7Ll zAmor$9hX5)rK6cydgDrvyuy&)4rFi$&8*Gr4W!cOeGM3-aIE#|2* zvXE22!xyfy6iITTJS$(aO}7UV`-no+V2B4Q_;fAhJm4?7V){hQyDo$HSW`Q14M>=E zdxe{e`8#B8no*Z*C#U5FBBGWhU`$%vZW@m2JB~`t0awjd5hvf<2n>Fg_Bf)li#Hcu zF6c4shexu4lDh%0Z(r9p1z|Lo-{US4`pCX~NgG0I6pEj9XDj3Lr=3szIc(zW6 zOcCEyPP6SUsT&1ZPbevZP1rBHwM>}6hkFb3G%rzWyeqm|H9?hHO5PRUQJpLH2d+dN zQ#aoiBFp7cd~WAqYo?g&vgg+8!;-b^Amm!yAyJN=vLWsW5U&tM18KeV1P{pN(p&LN zPAs$dmLm>=N}6uQIdVV^hr-2|ZrZ}s@AqD#BbXKGvijhK6$$8LLQ(E~>OYAL8zdRE ziGuGttr@Ts#cwgSbhc9TrmsYK73NSQ3WiVz%QY&e>#06yjxTpj&rhR8Oi`ov3rQ`H zn(y&|V| zqTpLz?J-8h!$0vGoeJ&soDscLl%LR5elVIQEW@lYx|D`HPJ>qFen;`C62{uOZ))W< zLgoU5T9ir)Mt)04ArsTa0m{^%x7BQK3HWG6}{Y~yM8uI*O z&Z`fM*}z(t-t0~&iPWhaKHlKR2BF*ZTXY=-oo}5*s*cmz$*iwy5E2{Mj9!7f@@RkB z2^bL~@`>RXQZ}WdMwCt5%M1%5Ans77zNrK*=p`WvaQFo;&})(L?AOX4jhNeyfo~C9@9BuS3fJ6bg@el`A5V8A?jX|NY>$n6lSpGui z2%ukOj50_o@235plJ>{#`mSq86=0g}ta^ucujCahWChzBkbd5y3REY>(5P|VoU0;w zXR0$?fo6b`;u|uh#=TA@)=8}h(N3OP^-7g9TT`r%z)n`4Z06{9L)zvEGaY-Db zlna_N$4V;jFuMp!+k8ta^013jJ5_?U@IWlca%k=11Sp=EX<7B(bOPd~-yCTAo5_~M zs8j^KAJt`06{rc2?`=cQIhP{CX8h5%34+fX-qn)BG)<;lpx&of2LML2#Ju`>Rixnu^Hf&o zAPBgsW#_Q{o|9+QPFG@A{mOI>f#{Fo9;>C;@Z-!N zP&~YDU*n{7u>jx3W3hdf#qHW1&S&lcC*SF78HT;Xk5?{9)1CekifzLX!pdn38wv21 z#ImkOgT@7ZPEFFLvQimKG#aa`LIF+~Fd(rJn$jmiECqXv$6`fAGR1-Xp!Q(c$D$6> zhy?Z*8G;=2SO3KD1B7;``Sk$jyF18Rv}_A==my*0&a~J3&R@S6J;+`POY~PbxE+~h zPYidAPacIlRa+04DYr0SxASD-23CK)r|!1_V_l_&GKqOXD%JA@r*G>JDq9mfygE0H zOLMUq)zg)O_KDW$89B>bYXh&|-W&5~Oqb}}oGDY2hm#+N=U0_v_h}Yi{qFhwS zTVkb`@PaEy0u0JqMj7gI2*32e@kqD1?Qt(6AhCUEw#ecd#V3)j12uV#P9r8het6Ke zE?e>XOvonJUD$Tbh=cr6jx`4#+=q01BT=OvEF%6^IrL;lMKx3*uNDcIkzdaWE;6yP zR!iV(uR=3!NN+@Oeo_85B35Uo&b@@q-i>>Qsy|^i{-oDK#xULRS`2#< zK~Bfr_0F;_VhbGCtg*`OP7!E!%ic}xc%rP?`a67p!Exb4=(y>?e^*-( z4i1pSiBOxGA_WC?$IO;1;&|0uZ$*D6zL-=HM1c-~DHc?9QVJL=eobbHQY|6uLeM(s z76DhR2faLAZB>jiH<4%YR_(}gxRj0X`kut}+jvVZO#QvbU0D)(B?5&eADuudK0vib zJh*1=M+omww!GxeWybua-c$(noJN9YP~^Nm&%L+z@d=Gp(YU(_+}S8LdR6s5y#NpC ze2U7*m>3-aPurMN0rRL4-Vfg(_@2uLIPj=I#43L3AV$1veTqZr{#%3&WE4FSt`MGi zPazn=Y$QtFCRy-UD{C)V({ zE4cYY7{TIjSHiFGl^|1@1+F+4euWnU{0dLWX99>=rg8y4N1^V&Mo9tTdr<|oNS4H$ zRA_^9uAGPabTFp{i{X3xB|(QQx@_Co0?P;$!V#&*$!-N&n%3 zB%I{VbO!`3OA{K){Pg|j3c3SYi=G;uFmVte&7e={!=0}BF^e^T=a0_dpW|G7sU|u zH*wU8f%1nUQ739AFP;J^653y4h|CLz@BT0{Vbq>s`Z&UfuiO%`>Fpt8_I~u3#xKX2 zR28A58$9)CglmEW*p6XDL9v>I8vyZeFgm)TF4w#HSx`B0FLvh=qkH*Ct zDH)b@^&qPEVr`0Ac-*bV;O|ei8-E+B=>z|440@w!j~ldlUX<57Z7U&yu3Wat8FIJe zR2?HfvVjDN!1K;m^h)4)R?KeWqI5MGXQG5v@*fi0)&;+^ap=Mlxr#zP<0X1xA})N^`Q`hT6cQa8DFzA zCnhA(=>!{E!*6Mq6Ay?`)JmY4@Hwx5jKTlz;~k?B7k7|`-e<``WJmZ(MSFz66*=pK z2$46mRzj@&f!lO&LP=YdMtebTUhj%3fe;bICapv7l&fP+!aY#FXt?V#6z_y+|Iy1$2 zGfi?u*6Sb^vuAj$ZV=LtcO<(yBYiNXhv7EN%3R1B zo&y8n$uc9!;qOd^-dzWvwd))Hr4=fBsgC#=MhpgU7GeywTvl`&uzfXj=XYzslmV_! zA_c<6ykb14u2UiYgn+>@u20B*DG&GFAt;O2))GQxGX+*Utx-x<>8B%`t^0 z3DXyl@Lf6o4Yvz~whW&Nh^GMKX|o4mz+D6a&koX`lR%UxZ5s#p0+2ArlW>}Buc;!K zDXHRv!+@8p%$b#Opy7=@Cw#o8oFEKs+~5CGxwiFv)Fzy+OGL6E@+0x%ML1C|RVK3J zm=)UK9hky8toOAES7+3+&)F1t=TmI?nlltsU>5f9&CDC=^RnRI6 ze-G<)OY?E)Fyd8AG#Kn6sF;hqml*rLUR<%i>_KqVlzaL;-NP z*rxsx#_QGP64zfY&a4E*VG6R2e`NcXi~Zl;vH);98FciKX0rTYIV^7Ui}m91mE{|~ zLn)bg;yA7Tam27Eoel-b%$OPduO210A-6C&*9f^psT^*ye3}(Hqnn>gvGGi;m84W1 z8;w7o>&(+_uwJs!-XcPn{miI|A5Z7(e- zQLI7w3uTOgQFz`Mltv7_4ix1l>8bsar-<9E$n+_0g>)XV-kNlkNsjCUU!^@O4WV!9 zu+=U*9WwJ3`~rJ91|>b6r_nuk_;~yw4XKHOB`~Cpy0Km=P<$WN@<1^;!Rdi3z>M;d zv8x&fNT4^D6PVa=v2wg+_XiC`cOE9MDE=4Bk;HIRE|W{C&$9 zm{JC>Vlr^6Urjn5OC*u4;!|n6p$0WA1KAsLc>iM50e-tNR$a%mNy(^m*_4qIh&H7;j-M%6ugrNH-kt%VqF#bNhMEe+cOt1OM7CJLGKvFzd0S$HK1FrlztNXI0$qio4 z+Hi|FVq_@&hK|XZ;Z^_Z1mWNdZN*R_m)GHBprj{`DFW#oOQL>qAH7(*Xvy>;Ns=Nf zSTOmKJZmI+6chOUJ&1e?kGsbhM)dQj-^fl|s;=(P7TZv>@>nO&=#_Kn7k;q1&6mC4 z9UY)U$bKv-=9wTl|Kz!v&fA?+Mb!5*3P8V*@?%>AEfuaizRc_pbb+F1*w+{-d(Cy)mX5hu=M_x;dD87Q{|l4w(zxR?DR z(7T5udCV z6pSc+=)>Be05Qs0gPD%;x8qpB;^vak$+1>=`T;(u zpxj(GAW_a{=9ujg7=Zw*~v%*ir3}ar)gM*qB#RNJQbpS{7UCB1yVSsUPJ;u zJXUH|vnARYX&dpq77B_~3Y3g@ATF_O1}XlOlSPw53WAycq#0Ux0W?6oZa4kRX{f`d z@Wy6dKFjGh!x3-#Ag8bT$u0|$oNT)CpOWK1+X~6%YaRjBI1CP>x6YH35_7j?TRzom z1k-8BB(#f16kWnODgr&CuCjR${4m5X;1qb+})xGCYU5gH>@3C8jxGE+`{NC&}@P(|Rv{9}ZQDcMF zIlCS3&#PK{vh<|Ry+uv|gC<5hr%^P9{0+1(9KODU`Bcd38h?e1Id+)Z2gkbEL9cFB znq?2%Y*O3?7M4F$ePJ*=(yBO!Ep=`|Ok+i2hR%>fc-rqx!_;J7DonS_EFBRXL*m5vIkmsl~Di?Ev1MnAFlO&H+f z0KX;^Bm2bE8qiNvfefQgx4pn;B?5^SE-Pko;-;Uod0ZQ8+}?b*R0~sNoBoW6;YbRm z{#Z{%#6v|jq~UNx?v-FrF@S_~w~=?fL+gc+PaLJ#@rk31xmZAw2W7Bgg`0K;?#zdQ zxp{%3R7iuni=d)^C*NKEhY5Wi<1YEYH3PQV`lv%h0<6a*5+VGT>x_N_inNAkMu!YE z=;opP;DG{Sm5mm@&wDrY5jw)%Ie}*Bc0!22NC?4#$pzLE6ayOTX>fMg=#fLsWw{nO zC%2CWY9!hJOGAfKUwN-`KOCYpQ%|AWboAS>r`br?R657oo8fVV4BSm)!`)eeeizJ~ zK&9MRsQ^?eGsOUxI9Ov623`oCZ|z_Z9xM!y6RvZDs@qj(ihhmjd#()zNP=kzEjzN( zm1D+lgn@MG&^CtD2eJwQGh_g2t=I4NIC^X%Q+Xt>uh8#mG(EX?<8FL>uiPo#mEeT; z)c9nH)x4VOKUXCr_}0aQaBn@KZ?b?u+q9sL+}`N>2Q`is(iuSN_(BRFg_dfq-D{m!dHw@9 zMQ&p@n*G8`dKEp2?tzSxIBf2TReUVHBaBMJL;fV-0^*r)8J5f|Zp7boOjJosiG-jx zBNwzY!`cgUqCNCRTU6@8$Pw635weu)jrr4b=k>DOgkPZr4+~KT885W;`v-Iwn{=BG z@EoK3HFZPfj(AJx8c99pmL5YPK%L=NKmfFK+d z8f&>#C@eWiwC&)(kwpl~t{Z3s%tg-0f6EtbCXp`AMGl*7!u#w(#u!SHw9cbJ z!N{D0oh56pe-UPI!yrihR$RC}zh_Z7;)Y_z8 zp{(`}*<@m?5u2MEfyw%HYkr8iXBY|W~t0Eu!{P@<8=9U~6n z-v#4Ej^_IX<1Dx*rA4}|DD84Z^xVsL_!e9eYuHC*Aj;`6VewYtms%|MKPrAEhP9d1 zuNcRWK{~t^E~6&n+1k(W_2UL7p)~(aC35`goxEyJ?RKc&?@sXq;n0D`9>k=Mul0sG z&W;bM26X}v9?jhafbIDFigf?CqSvn&fI#y%6_HviuV7LgEqn&5$%fu-~`i=Qn@FMixh47!fcu1z<5dPQ^j*m3OzDN%|doU8I%&V=R zN-&f{wN$|(gRD?NDx@vayZ89hU`nRf9l0Z6w;YpProCa6rz8J*L^bx$U|?gdi6DT$@Z%P&Jr znnk9h*H}B0um)s=_?8@MmpBz9x_ns%oQPkMl+7fbas4K?Eg2+eSRKEX7i4YkRx!_! z%}{)O$4X^gfpu>ANqYFp7f%y{Qshk3etTvDOrkAj;?q~8eamaRO17(mzN0Y$uc zAQ|Y|TH|ZIn=HtWSjZ1N9pvjjwLMr8;2<_3;90e&aGebl@n=z(#2l*pP?6^U$C{@{l{CU^fqw{ew>()ZW|Ltj zDIb*$F2h3yeugPTC3ByUbWvg`9s4*VShjca4136Lgu>_^M}@kNc*~8VxJ)d1%gZ(uZM!v=_RcsD1C2eP2LF{TLfx{1%qGFnT@r!MF3k zs0MkH=~cty(Ca?Xq`c5LBUE2sM<*?W-4G8n{K_Df%KpARi>#EWwH0BtjK#oX1^p_A zaD`Ky2*Vf%`5AcF_?0e?1UwLcXIaFU!-kwmsh+HvSg!b)Dw}YKUHvduv7HVTzK8@F zg%Uq*f#%3cjV}Sb>Sr)ig~c*8ky-f@;HQlg7ZNboVH0c0KbM0JRv7h_^A=(qB-+{8 z^QsHjk|a?Dy)K@oC79}nqX-g}<)$IRkbh>n8RW2~ z8joRbEZEO_^?Jf~3$$&54zVaa8H?gtcc`;&D1aJs`JiUV=+{z$Rn*c#RCb7-gjK58Oq2wS9`pycaeCI1ej(#VP+E4hlXTVQpzx%zr zql~a91doak!#oobu)siWCo==hlE@2Y_#`{zCjWPsn`QoG&StDtv7VSqOaa|_Z_p5Mxm+Lbx-eDjrRGQ`F-r}eVRuLoBBm_B!U?30yy_< zxkF*!4KD&uC_+rQcM`9jyzemQ){xHH@c(d}4VAZ9rn3?aS_zSPcPRRfNxX!5cgK1E zp7DDWGvxR(c6uP>c0Dbv*7=7BlNU2PRBJgKtiwz3egmfmA&=;T@5o+v9QZ&>0~HZ0&C`_ETqR!(*KnPzKC z_QU!cx`&k;tJdyTWzd_Hm6J0w<0o|EIVI5jP4Ny{p|4;Q?|Xa3mbB$-LrH&1)yhfW z0`vYuIBdp&?xEM|5;8Q-OJYvkeQoihUnUtmUFCgQJA;-ZUKy+GH)S;=t=bI+b(8)1 zOcP_uw&`UOf2+W95Y^n8aj!RwWkQ0r%sjEcq_-eaQEXsBT)HvcB2WA<_Mz2dv)!yi z1{73s{Mr5fPwyP}@wsVns~_LPRYayULz&#Xpw7G;28`#^*zmWB{AM}O%{QVPSQ%2(?BV0XMHH; zHa^tuGcRJ5n~Wl;gt(rUji=<8=kc>m$afM<%ZOvHO`GM%g$CG5s7Uu|%<%c{tsPd= zAYc9XiA~FpB7;u@>?nhy^sjXi_%q+*@Z{;>G*f!7aSfK0BW0w$G zRYSJ2ySs2+_o5Tkk*`(^I@L8<&(;Tm5);+*EaxQlP=`O4o7PMXXFh^OpM zX?9yO>bHD#?IJ|A)fm}78+8%e@ zvSp2aXbwgsaq;ul(Eh+$$Y^zMnW^z3S$K047EU<$r?qeZ-XrMufdx854N55&z)~*Q zZU%zdgl??&`}*Kj78rkU3C}f9kIG6RS%B*tD8~6jxE0l1L?S02 zxnMKJ0xclr4d-NYO?2}CDBH~L>l-_3!CuN7q~Hzq$FKO}Re~Z|n@UrkBz(0h-Mh`Q zx0|!JYcl&0-`}Vs@a403Ms=TNoW09}omMZI(#Xrg#8ua@{~y}kIxMbcc^JgqWw4;h z1e*{%I0=CeAUJ`+2X}XOf(8O%a1S0}Ah>&iy9Emb4ek!#A@9Avdw0M6cK_PvfjM)! zt4q47yU%o2)hSA|8gnC)dk3lPmsQiD4benmp)Zdr+p|S*R?O^_Ma%N^w|5`Ckh4hk&n9sKN?QWR{%?5jv_?DB+K#O zsM*v`5uV&_R!vueCy^bSNQ{##ykJ>#^R0K4E?pSp4}QFRwfXCz%H0*_}(*eDZ&)+P-L6t8~MrIsL?@BQEnuAKWsNxFc(x zW=janV2+AiAs^uYn)gTT&BGCdi9I-@BFWs2>w1^2J_i?0i(Gd2 z1>nx=+f3qfQrC4#yIm@z#JK9DUPw+n?JLq5$t~-!Q%K-+7R4vI04!(oX zgSi8q@y13Q1eSRg#AuH{PhlE-q6nb_S4egeNT_eJ`4zHV&#{-f%`!uw;z=CuPb zy8QcyBKaI^^$-n3so^H9j{26KlJvx52(ob5sUfsoil;XxhrtQFV>&SFKhFFZn7hg> z??K?ZoYU~w=b*eU)1Ova)52<@MrbORJTlW^hfsQi5J7eKs z3iCD->Vzp8nQUkk0r%MJ)Uam<{;_-ZUT+{*YRyeNHm=82(Oh3~zgZ_}z7< zfeOU;kDvsqRg|QCsW>yd3PN2JbDoSZ*Ix2u&TuqOHf(bFAP1=LWb>1JRHo7X~Emc1kJW0>q zP@;P>N`pQ<8MyIPWtGDgvj9wA?%Iibo}x{XOn6 zG?&xWD%i6qG1d8bQ=YE2G1ks*fr|`yJS&|UR_Nv|6#IomN^THXq!aLoIyho@oZg#(K^t&|e@V`B5v)Qj1DD14 zR%L{djPo$6X{IHyF~xLp&CCBflK*w|{(p50KVv)*Kik_a3WpH>x+sc;f^}c9j{a10 z9;H2YszevT(k4d1F@1z*n9cILFONE#NU6dg%>jye*dbTkQ>ku6cjHuheF8p@VjSHu zZpp;lnpqy{+q%ntJ=P>w&=a>9{l!4?mYguU?sg-E*u6|GruFOdnnb>+n`h_SH z#wOwJV|}n+u6x+gsd6riW`gTleVKXoqj9vvE#HgL>SHmV0=JlbzL(Gg@}1ID!*6$9 zDoY~FB#&Y?)^cw*LeU_39qrL0U0*zt;xli&&Y#KIi_YlXE7T8QpW8J^pi zb?_R@Vx#PXTs`?T@RjT;Z~f_?uk#c4Zv@>t&uJJS=Frtw8l{u0a(B1^xse}Y@NZ3(UfRqtq5rKHExoUKOuaufe($%+$pS~O@ zc;Dez{)==c_sqe~!a{RT_&d##in7Lbk|&WQt_J!;VKs?3Lwpd-q$ypm5i6ao@KN=KXwU`@*yjc z4teIoOs>l!(EsCu)NZ&=g}4rUnKW*@Tp*ZD)9wS9*n5!h>?pf)CHfvJR{w%o{xX3I z8Q_-={>OwWVbIv#^6^nw8DgHiLJ>`6FL{WNL`a_%LM*p9M^CUbo{Uaa4EQ>F3PaKz zfB9U6&d$Dso?P_a)KA<_I+&upgpLFw)SmU{lil<*M$La^hnRuay@zXzg^FDrRW7uj z9T|jc4JqI1SI*~}nv@(7J;>Qr-sk3dnXaR6%>dcE;Oc#njD=+IO0`oZ4}!`hcd2cW zT*M3+jk;z-kjrHh1skh%BbvxGmH(JsDi;xqYkuXGJoIFSSX}Cs*Rj6F5ALS9PkFmD zi=&DR^pGjka4tcQrsjzXQQCQ2$s;)P3lX6`*t}sY6yc4PWP962UF}crf}pRlG>0(=}+Gx5|r}2_Xfe z%j3%JSckZz56y|8*hwl(`_lKjk6z1=&@OtkNBKO?i|8~AwT2%nz@_re7R;JF)0vyt z@Rgeuu_OC7Yb$l&g4NJ7F}Sq{^bSVqCN>~o!&R%u$%xbu_%y`i%a|e+NGig2rA6~H zYHi42*k88mG_ngH(g;5;@gtu#9G zZSE4;j-%oOr-7T)b#{E^O1%%@*p2fHkmm3@20}?6G%GRC2c?%Xm%e8Jn)#qJ8 zJ)e=VaH$F9s>=#Bdud(7p1M&?)+KG6Tq|&RurFxfXoy>8#%*Vk!#9iXRaQ6i)7a>9 zoSSLjqJm*%DOS-TQZY;HxOc?`OwvgtfX#Fo6#XPmn2*KYU7`Xz@`cMvsQ297+T@cZ)b_}wighEC5~?rOFkhs56iHf0#ykaT2`2^4a;J7dUg;4bs8F%nomAo`Wn7jb(j*h)MnJL zJSY@>>UhM{;kX!gGx=xQq|Q04@>CD7zP;pkMDVm$^5i4L#C%uoHd6&f`9{7}jaL#8 zl0iE~4ctA?&!O)ev+_`}mT!E|j=$dNr?BM)Ic@bfxXyJHVxTYn#_H4Vc&vLbkqN+K zVU*WX)jKhG5P)y7>u#c0&yBA;W)Ha|usqwy+q*~~EFIYI(|vxXUsg3~bZvgIFoIt8 z2ZadaZL)Af0Z4;{)FA;p!vq6acpfex4;gfTlso@i{-^k%_CLk`2qNIA+W^SH^Uvj9 z(EnWiQ~Xf-pGN;jTtLXve;D}75X%3~7Z45A@vmYa#Ol9k{HOS#?SC`I_#oW>1y@L% zD(y08D@m4re~FmjxEW2W&ThJ1oD}ZLPi|Yp+JRCv*rGdp_YV4V##mrJF{*0aN;=MG z`QV8G@=t@MGB>}c5S!bir?Y+KnbwPfxCY2tO?K1OK>L~mYX^;#dh}p)^HTtW1{h*< zla*gSnL0B4RTTu_^VDIof{#id_CQ zqrt)*xw>60r-Enf-ST`(1jPV9XmGy~?92t{VSn5m2Gb4d(Jjb-x$hZx3>gqY*pb5D zq&HR1&8|TtHQq4qai9JhnoIiW-Q9@($26ac;rV~VhGu+nzippgxhsfN-dG`H0o;>HeuCw=cJ@C**~sg3S_o6esT_VGC4k<@yKi$Z`3$)5%%@eyW;IG zC^neY-Tat$|1;jslAH6??;GlloeRBT+Ft3^;{KlekLnzDFO#J_RiiuAJl#Fo8$r(j z&XsoB#x#q|*>40Feb?{3E0yMg!4SuJeLaee&1OdAer>MjV_VM@+^s;N-y`lMXq-Zi zgP+dde@VZuaB~&6F!p&Qo^6;z^JmVX1eH4g-&orhZdQ zzY@6R4|aXFc)2Z_%1Zf1-eyX}`kkIRg$yAiOC1d{+x*pi@g+yK`izq2a0-K=&l+ix-3ij4l1-d&N9DX|QS&t*@W|V)dJGE#IE3LS+)6~Y`|M(uZ2yz(Q zFv#mV3h^qtx_lWF32wIe)y!Zx+Tn09*s#?M~iY!Pz63Wbp?H)+RPtV=FIL;jqmrVeTrI+_3p8*+JgN2 zv2V1|k6Lhrw+VcmJX+l0Ar+DO`djxt|@fYuqQN9puRRww8%)DEjcCXnr04D08Ge0@$xNMC%B+oG5j zEi#a1BVYM6@$&S~rAXIU`J{Pj^qoEgPMlN9ZMgMxl(d`m7u)6fv(oe0>ua}L(pGvu z4rb6kBy-zP<8C!TJq+x26V;b{+_ahN+eP>$N5_ElCQ{Wps^{tW3RiP?F-WqL-3-_g z8#HV&B;L(_-peb&`O*m;$Sc7(YqL@?0CEbp7}ffNB%o0-kKMO&!L%&|A@Fz0m5q^m zp1hbT3(>`{R-8C#G^`L<-GZ@J2%xMQHH(^U7L!Wx;T(x*F5`>d~|6=H3 zCV+3tL9#W2f*Ii&v-p*VTg2$bo$4#oOcmw@ABhFoAlHrs^XVJkdgvE9OW$T*bhnKp zi0*4g-ZQRyK(I0?$!kiE11dI@IlztND+>~kQQuJw&h~l%HA1O8+p7DvynWgsKjYv? z77*1B_t3GNWRK(@#*wIfWD#rW#3Sr%9z47ekEsjkD`u|ibs88Qv0ga_y=zk4eH z*=f!z)T%N{9)n1wj-mOh;>lgBNWk%jxh?}p%9?x7XqI(G_a1!vUfVAuM0>(mxXTe# zVeHW{Ln>Ba7k%)BD3Xa>%)ojf-bckc`hy(vX#WtX>nDGRi6(KWPHFwt`*#@!Bh@Gs zb*x4Ah{xPvsMLjzE1EXm7~b9liC~0^c&pPJ`h7ia$vM>_ufV(i4(lZ9|Gk4T8o2a2 z98;ItHUbn0ek`Uk3PG{&ttJV{c6G^jKi*uR9Ml+uAbw@L~R%PVfRm)90tEZNGcV!~2 zzQ{?0^`JmjfDlRKhhf^cVuy-`R^{rG1|rw!TioyOgfm*^e=M^T_4B{DA}DiQ%n4r`?Cpw^ z&q;jG6vmgyP(=K6FuVLmmA$dnm$4`Cjqv#k*D&;nWa#3-*5?2bm!=`|yDwj$Y}&(o zpngf)j;2KJ*XVRIA83TUfgafC!FMS?&>a1~ zB9aX~dE%*Q5(sSs>pBL^fAQc-c`_ciaG9Svo$3*N;w#5Nkv3h1grH3qsf4la8L^aG#-7hhX` zd#BjMHDjLDa%M?Y znB%du_eayVpj0@c)oBY`)cP$S)=qVGH0h}qF-%#`-pq@@*AOi)`!N$xjuw$CG(LNz zQbaypSB=gCCIzJrh6dC1isr@v?8C`ETFx1+PwJb(N7m2x-@b46ENg+1)IiE;an^_9 z!+-L{gqiWMeUca7P2+F&UTSI-mQ114T9NcVA2$qR@i-E+uoBCJdN}^9<(T6O&0yW9 z5%CToeUdoteXP1qr_p6;1MhO2LMgoTJqmb7Bs%%r?}N>k9v`C;N#%;b8tB4|d%&OJ z@?)e5e@{|5egEMG;rms?z*V$FvvIfIj#q7u2$>(hFYTmWj0w@QcFdNc)d+2&8doRD zu21brNWS!ibgnN9KH1DTfoAcJx9LpP~T@hJ)PCk zSaD~r`)o@E^^yE_hBj#0gnJ-|p6mYmxz3Kc=+x#ids_?ttm|TCHoxJeSYpGunSC9K zxW3H`dfn>G@7H&a&YQ$PX*!oX=jDp}_+Wa|FPG#F+cWNa5WWnW zfMbWO8emuPpF=5mBr=ClG<2jwU(pBR$VfUm6+r5Z)Cnzx-ZrEbbbp~>D8TLmGynPf z??NoD|}5N3nL-3&MBz6g_(15Tcgb{JU%43l3_%gur zIyqY?{$bPcNDNLNZt~lnZVC4#?Op%kWjAlan#0X{!9sA#7&r(kfPe+T5nuC!f__JAq)nDLvo|TOY9j-hI?33;j2y#HNB* z{giqXWFKEYgmT8!ul0z}dLBL?UdM-?z`0!1`AkPQOd1^v$h?gE1M{kSjXhZwxs$)J zV~a6p27Ux`L-bN0pg#tLvdVZt!suyrU4G>G5eD| zwF6$2j22e1M8C0OnaQW?uYF+lmzw z=YwiRDr7u+M*(pPzCdHfKFBx|Oz+1o6;)t!b1--79y}90+|_Jv3Y|#-6TCWzzpN56 z51-uC(b9h+3L5Y>`G;&}NZZkul-q$s3+WV?CZn;8eY{i%YFEOD(95I_!rNCZ+fjDI zIO}{}%VdYRf$5Mhldq1Oj%Hn?n@^xgwBODq@!p3@AiQM}<`Max1sv6k(3LIe1$;C) z{&p2`#L~PqXL!1w9MAU^hW(-#*W=x3xsP2iP3$Z0J1nT0)2k(KCd5IYCVs`*V^<7z zMJ?JB&)yB~c_erO*bNQhyE0~+&kYPIDN(PhoV)j*|LBYOz`Fvh8cW+Ip6kt4!7RAw zbIV^0u1{=TF!s2$saWzaNg|AKA-Xv!EgmQgQ;;mw%w)V zEh`h*gcAZ<#qO`R<}6+?WDF4BOGkAweEeI@x0BE*TLJKA6b5J|2E4-bZXim32=(Pn*mRLFT|kK3;lVNR|xYx=4V*p?}VoGeZN*d>90!1(U`3lW61P;*C? z6jIE`<=?psCctF$oxR7n?J^*|z}EaK$7ncuQJA;1m$R;{N&fz1LCE@Bb0b9w)i(&~ zb0hy!B6}tNQX%s-*Q<_=Eww7^72o|eRxd?7g!|2n8Ux$+6%z|`z(Bl+`y;E7`v*== zc(0F{T2SrKF67+JaI%QoD_=Eprd$f60eXF$yAlxP{W7lMFr*;?onpDaNd2a(pJl~? z*bRrcKP?h%rd~6#vd=oG&qmG1!E~@`KjGcAtUuZ;@`@gDaD&N*dCJLa^vX@QIv=@y z16!$pZt3I|Y)??GMhH4Ebm2RJdA>HjCRc;UFFr$fP913Lma&{AbId!X3KMD-{&+qW zrjcGve`?VPw^E(s%w=aM%vkdiQlEsBqZ@j%~ z=r3(cu*~%A#|p>$(cX-wKAXmY8Nl#<1U>QU$`8Z~2r<)6um$r}(;y4HE1)>fuHlWRn-u`EQwE*?4MC$8y># ze;l0x^X^&Ok)?ba_id!Q`;Y`&!-4i921)--v%Yr0n(rm2{!sG?*N zN36*&&H66R&=jqrb)wdlL55WTa0Tt*{`WzWS3btZM>3&OW9M}3#zi2GB}LJPTv z;|IovXktY0(V?QysuL{;NIHG5-78x1lsPgI!^lcpz07zyj8 zf|}-M;&!BV!7ee|dXTgCQ7LUxip3-{cTL>tm*#C#{(KQTEoKNa6i%a%0@*8>`N>XO z|B3C*2|81~U0LYEG~FD*deMDLj|_K+h}Hf*m`T`?1rJ(7gG!g5oh2==0Ping{-toL zHjQejQ9%ds!d%XQ&U!V{e?+`p@!}L`DWcxbz9+N!BJi%c<-%pg{T%5;ztH+ox_>wZ z6@+QxPmdQyw!Ws%t*BW!9z1;`dq8bc$0j6TTgHn`9h*D>p+#6%yr(&qq*fBg?1R_Yk*4M5d_e`?L?^ zH<#~vD<*V;LHz4c-vdl)U63{i5x;ejyh6GJ;WiW;^Rj4gBScKhI-5VuBgja**U384 zPJBDc^hNGG9nyjjqVVVP53_m7tauD^(^&W9bk4SpFr|-DTy!Ygwsghbn6z31pt}CE zNTi0#IUEd(xegeW#!GKkq!g9SqN+9(2t4G6if7|jyYneDIqmY9`rKct{wCqEFW*MC zd?AixuB@9TrH43FuN;h@4~}nk?Qwn$=a)zgZ(G58VI+tGim?KTLIS~Nu*)^GWQpFSVB)v6%~Fd++F#m*e#E?k9u61@mZzFv=cyQoU} zW9p5E4KjLv?GY7DDe5DL`WidZhjvRHT+p8znpLOx5oRoW7@U*|A8CW%e~r!mXgU3L z8`J??!Xlp_kAm$L88A_!9P|5DeW_s&$&j^UgS-QYuJzr|3b!WZSBiYhd<4N>n2?pM z{gRb65S0|zODmHg4#av%;$SH2Xl=Qc;G=XPSa`U9v-t6*`Y2I>Ig>xE9gTbyhVe7j z6?G-O|MuK)q&7PKfRk8{ImLsaC0W#Ouh}NMwzRsY_UKxys(E*-xnN`Q`ufCe=<-&< zv{-lK5LvGIJ>|Xpd?igIN_*w1OUT>CJfWVWku}f8AwOtI6>70=CT zvcmA!;0Sw|M;=}Qv|DwpfMg>=9BB7AOx13OgU;Gw|@KSA; zA4*-q)h^Pn1M`MC*46${gqsm~7PO)zO8ZXD(h-`D9{FU4!)6jG}JLI>l(Eq-xBOU2M@VVrs zvvDpxq}$b7>X+j$1su?&m%Me?vSZ$zTjLjcvdy?c z9I(q*b?>D!r9V7#IEOlmvG)ExN|)L%38TYJXSj*DyL1=WPSGc*_e#BTHVW4B+Ay$&*2p=*#IIokFyUZpiyZZqtz z6zaQg0ew+OnXZPTEU%xRqQkvaA!$BmtlKI_@{Pt^LCuBMp7k4TkzQtM!db3g zBQkBJihT@Je~9Nu2z)o(DoCb~{Cxm+U^e#96TINXwe=XZY(8)eZ91funLO>-6!ix$ z!!9So)HB3iZ?>vPLj@2sB6Wk}lu9C!_pS7uXOFg`b)sIB1WIkRzmj49jRkVdGP+5? zgR5NT5>R94ZzvMmBmW+SA=KELwYLfs$g{cT`PynE0`rE&eYP1EzH1?P`=tqFB{XMr zq%c1-)u`>jDi(eWm02zp!Y)VTNVlE)fml(AQlz4|TZ79DVZu&uC}*1Lz4aDHKauOS zU)>9_rMXS^#+_qwj7h6r4Kxs_EzTY>aO;JByDi3j-Q61_CuWl$S_rk@@Rx{H_{QO2I_15Xqg*#0s3V~!NQ(*gtH=E!M0E}+ zKm`R7v!OROEh`O$A|}o+;+1OZ9QY20qvh*Lq5LsZu`r7`{$23A)n?9u@;(auE05V& zr~eujt#kqP?=qRkKmq}Aii+<8zo8e9M{+byf++2A&ne2BRMk!nwmJ$AIDr=oK*w%j z^_;=?3_)FcI+$?kt%5!n@qvL}|2t|JX+@qPI&P|&4z!pNFxZz_`1l|RIP*EtRm>PM z2n5-rc_E*w=)HZi;JUM{97d@R)_bYEM-)k=W5!Wh$1Xl7QS3^29tC1D^5r|LD#ygV z%qXUcCi2h^bzq+rdL3VK;)M8hgc3Zaa6sgS124PneycI&f7nJ1yclL#ceTEm&`r>S zBvC=gtTu6oGdE}%jka`SETKb~v&B91f;}(kQs1ggrEGxu7mV>4gOXoC`i-&CDm;Q1 z@>FaXDrbz0@PA1IfR=eisn=$V`BJ5>Yw*2gb277POX=BRb)E{MsH#E>-Ih3jA z5Uo2(4w0*mzf8p1?wV0}Ofns?W;y_~V6N_UH8v9QMJM#>?ScMy^SE>?{1E}%;f0_x zp`h5en^!Oyr&ww}-k%=O8CDVw zlf{$R7eXJ8vbS8;D_bif31c@Nl^Vtxj2lh|nDjGO1fuWe-GwyE#9%B`X?!J4)D>9C z-#@5tMT0zhOn`%h38cSJkg)h_;uJ_L(qJEZ*OdK-`e*r@<0bv4pRwOcgk!K$?r2=v z;bHU_KGH1r2bWZbn-q%7ki3`cMggg2l2BNPl#u^p0=OWL?DFvMF}Jh^*B#GJz_XQ~ z(hK;}n@fck3?u)iBI^!1KD1%{AOZN0Bvgx~0y z_`L0`^-(RHHoA#>q7uNAl|x3N zMNhG9h>;$2GKK*ikqxdzf^b<78J!Px)nNIfx1pd|pg}%;PJvZSeMGaFo}Q{cO%IYm(rFpxoab(8iSSV~-=$5j+e? zk?L)6@sALzk|=URZ&2k3{H5M+U8k>B_|_uyU83?7zoH1Eq9DvsA*fe@tNwW}#k1Ny z!K4skE#ucgQYslr`8aUj!{;!wmq;KR|L&saT+}2>bQnhAi$wsOBC$4FTihoH**XAh zDcleBU${Wci`H-Jtg?8LPCX;f{v=3#02@XkLAeL2C44CnH=rFnthO9{1kv=bhG(N7 z9W#V(%*nY3)v4@L4W`;3Z!qQdHxD#2OTJg&MU)6}@&Z!uw*_*aUdhVAIRg4B)&86& z|18lsBGZkuwnmU)1;Y4Bg24J@(rqI^Z%`xS9^%I_6uj9^5D^p~!xgfnlk4619&)7j z9zm(zy^q;8_77C0049mmYs@r|{1m%KM5PozFO-{`KL$tIrkGVX&5yiWalCyB0R4%C zi}jfV);r^>cM;D9#lw_R-U(4f4ZJ1-^o?i=s=<9$40DLg$;BAL!`gcdK(`$cRQiCu zOEBQ$IZ3!1=xFDRfW^SVaUl8gue657*yf8hP~|dMQhqi0{}Mw(07R4ny7?hkYvMZW z6hn6J)5%@&QJ%cAr$>RlM1mJKc5pzkwa~J^K1F5Pe+5_rRvQT>Vr1a*8F^|)DT4+1 zJzl~85Lp`Q>NI3d0};)C08GmttOpb1X<>tRSRl9iV8M`)p(|*hRF;U6xnETP@d*Mb zLmuuIXBh{?CUrw=s5J-B+o5eJGAxYPCVg%p0f6G=1Hkb!RShUkDDRavqBv}P1A(rU znr3>iJFL&tFo!?o)5*_3swgHEN!vM^o7?w03&V1$9&~Mo&y7@jn7ax-7D-0AG=geA zJqcBWIB2Bz{SvUEgqBDd&=9H?eyIFhl%tX}wpX1VG3NiF*C8@d&-fLQCX3gWl-(_A z#I)WO{m${@H_rwMcT+YzR<3=L-bDZIO#Hszzke*^p|ZW}SXTT74eB5Ag9ahVx9i|s z#YIOr{nfq6f%mHJNSk!pvoWotm>3xZV?4rwP(35H#F2Iug?@7uC9%aZ>AMfuYjYLU zc>fp$(k8J=3K(5GS{>5mQ#6G4rv;4Hj#r}WFx0CFV*{6_pk zj20S50KxNa??a&ZP*@Na2{5&xKqNB)!0ULB3Ry%K2Q`H20W#NdHVPcD(W>Se2URezXnDi;SwVzu;2@~lin>G(94@Yced&6N00w0$APEck`|L$ z&o2Fos30x`bFM^G-`~WA2Kpe5|Jpltcyl~1+?ykj=YAITAKnB>A)?H~<*Q5!os0tl=Q}l!YYacnfa!A&aUR2 zS5yU~XZ7`J7SOR?CmUL+SW3uKJbZUkc^loUxt$xp zR&Y36Fgmy7eH=4}kete!_9dhI#=n|KeElJ2UMLU@UYZ-m4zEhO*W0bl?)bpUO%49aY@i+Z6m5{g9;*m@U4_;$7GOF4W zmybB{paI=4`9fJ6j;!X80Hu7=hV`zAv7CCAFhPz6yko2H$M)l`dB z7IFp+XOR%<`>HUJ%8!~Zz4OWWz&lR~GvNViE%lO>AyhB!=AN@|Tg6D;fDNb;d+Vy< zB=!3i(7-WQ7P-w#oAWYMTm(@)23*l@B6Vp=Jvh(4ELSc=Th-k_cDt6~P2P@6)a4|y z7d976)IcJG?5+V+f0cmg+ft`%HKr**;;K=E2=fsFGmYusz+WXR&e0bbcg}t2h^Umr z*GOsp+i$lWal_5(e}1cT=wk!tdAW-5>uLFO`tOD*U{tlOLLQrntunGd^#o^D@X|3M zDPL1#@%zK-aYk{cv78eF^3LsXC*z`mMe+NyAr|L#z&N3J1W!{I%e*d}Xs!8*F6RN= z*>0UkfBo7!rm?z+n6*tMscC~?IA0qv51QSqrim>&_FoJ2fmp8{Y_Ko*-LBPr4#?Bj z!cFCUFF?4e3$@l9YpuR*T5YDKhKBtig3s4@+Ke1oNN=45AaD@8Y5v{f)wX(?lR`=a za0(W&3Rd6(~~K5J=r*k-Ew$c^|)zmyM(=``#;ny$&j6s`MfFdda86CdX1 zrvLm3@?qHFMTn46ZTd`+u)S45frE*kdCV+01+0~Q-DYDqn(D5h33c`vysKkFl}n7G zc){v1yNxLP)mc;HwV*ej9S~ATXK?wYBCgldTYJ3^#dADkpc&>!kj9njoo$@Tp69$VArk$YqC zrVA8LWAAoWz7h#{v^#WdcM9{^;qc`QD@>EDqI{MQgq@yepxj((`rH~`>^=HpW8B={ zbluSdYTJK>cn2jcFdn>{C;hZ71-+8YS!X`A0zq~kJC1v*WC3q9n5v@$Z43SWp|A4< zFil-^D4yn`4f|>4N&rH&EsjqlJjrb-rB;7Q1qVLTRK;|$deG-}ER12? z;rS_FrJw0Vba}{t*YJ}_gt27CFbm&!t8M02Kh~U(1k1X|+`A4?i8GfT+c;oa zRcDeyrkZN;P z4xO&_!@mynS{J*!868i-NR7BN(`#Yaid6J&_+cA%TaKF%6_idf!0O^`U{GO~YWL%4 zZ(%e5;aEF(e^ZPY#owNa>u)LP5z4}Yu%&a`DF}Kod)8P~;XqBlxWCE+(Cc?2{U_$zCsM^o$c40M<%iF7pGy41 zJ)3=$eyThyMFbs=O5Rp0^P`@5cZ(@?9Be#Hi80L_+;uzEe@BURcaH11#xe8K4tr}u z>-G&A0i=MAxO{J|3*=8$M**;o_=jLY70~7K z{`K|Y^02u4pDzy${skt_-3&B>}L)3jcLJo9brg7 zu?HGF%2keV!dC?4>e;j39sgN-iJ>`w-(=zCSQ~};bXDPXO@BMOT=%~h`JS;~QVml) zb4!vLd7hk~Q1+3t}$I3Yo6c7v$F6>_)VM{=6^1sU) zqpSJ9pn%2xe=qv~vvo}S%JJJBt_LdsGTNq9pk`ocKPYIPFtSvZvvAw9Ym$nF63-C# zp-=h^D&BLw4G;N!=$;$N!~gsdk9&oU!SKW5i6b+FpB;m36tgqAeLXnRif#Z;JdC<- zrV6qBprCppP-Mbe2;Hf;tos+~J980Gz~AQyjSj;)!>{TqTOgwjJ!)I5ZWLR(qL+{f z&L(4POF1FiGOgGL6Z&C;pe)<`WIyg>R7R)ZWzsa(*|WZA2Rz^z|0PZZnR3iKoZFA` z5ZcQlj-(;+J-5lNHaEEcTT2MZXJ5{j3-2=hVPypR`R)k^U&yDwXyp+zWyYk~trXIO znQx8%U3TG(u0h=-XpN7qDKPx^aghKzaD29$A0oL@squZ+5qJ^Jj}9ttKzBL!6;8lv z4zYQ(01D`xQEv*E@@Jkar^ESd%>L1yR!|g4C zDOWPvGCfR9Yu&s?-4!B|_X#<9gcr1Hj%PQfK*+duj?{C*rOi2Wkli;0TbOAE5+iGW zUQ|kd3|0!2x(DqNlCBi?koY1 z@FWiv5#goUy?1H6FLv>G9tB-1Ow6xg*2asv;6dKK+wr<6Y45-t8j8#@7h9?wf<#aV zr^}~6__56M`(j1DX9!_{o<2Pjy{U$AQKDV@8$qKcGEFO?JEcDROe+YZwna75B<1AZ z;XeU3XER&~POggm<#O~$Tf{_pExo=Zi%)aj{=&=Sr~R*4E^g7lO3k;$wQS-F2XEvW zclEL_dA@nk6wFCA=l2LZx(ic5DWOS8L#R+hJ2$JsR3?}aa(`Yk^F?xP1hptw<;_;s zh^vf`xIItkmtOh?j|(P5?-(BRNxiv`kGEF!tRSvso{>FnMxv|Fh#HPAuY!oJfqvUXVO+2Ka0pIRHkkh=NH8>7$a3oqjgxQ{ z)6>L}ju-SK4q~5_7(Qq_zDso@9nXgi7kuJG#RX(nE%6VbNK?GJ+9s>` z>U(N*i3KGaJ4##&^@xo;&XsSbxOyC<5buBw$#3Tm28m1cwQ--h5ECwDnOV>-yr!y8 zwbeW@K0y8z>srQc@%erEHf+lyU8+1Y&be@*n&#$Le|1WMCFuG(gExV~6|2vT+iDxG zlkg|BG(pDp5!o~3SFvwH#J=vB!?xaAwENnzLf_>& z|6mI5UT~hL=(1scM@5GkH9{#TzFHlL&0o$}PQ|%Z2=R>S_K0>h!d%69V{nSU)ys$h z0tQXKI&x&MWlP<&sWNf>*W|K^6W>lu#?);|@~WlWQB0N>=61%Yks}2|&s4YN-abQ( zBQ1P2@~AX>hVch0+-+pglfs z=`sYvl2K!xHO=QI#(Y9TVnC9Se5IC}b5~7*IH*rUA#}v0gnX-rhCQi5`R?BDj3Yvu z7#PH`ln0{<$i1E;gTNn6GQ86i=%jE@nTv?}f#?fyVm{zXbov8J4$z!V2m!J*Xr&#OmG{R2-LAcZlJrsy8yd#`xLyRocs$r|mGoSAJgM z`mEX*a6HYjm{F3uRLPh_<)v0h%rEjHirzSLi4C?OYf>>2RYx^DAJiw}LTLMMH{u}UO`%~DZA>EHYtYpZ$Q&+rVik2}hoBaJVI8W0+ zIBVoL{;L7Gwj7e<5l(dMe{?*BEZ4~52uumnVOf$ATw}^3T(moku&sBw47;=m0c%C3 z8^#{Cc+GhmKNZEAjw1F>DDmS&zLBMShxm6_xyvp{qgq{?;=zm2svN@&y+~oR>hUhz zh%4jdVCNO2F3-4^&z(MDK?w||inRHp5Kuy$lY5SV2@Kr9b^gA}@NYdQT7E`2iVi5& z=3?C

3qxb(m|`tV(890Sd$x-*%OV1i3%W*Vhl2nSr^v8VkCEI_}b5snMIzZ5ZxO zwEO9|?qH6=jUBxQUyK??(3+6t_&OfIc*dTdlRfguI@=`m*tGff2nli|;TCxU8-^TZ z?@tKf-pokOH)3c$O@4?CyNq=DAmfZP(L$i~#Q5r1=_3?4>V@hywxfr7(t+>Wlu}eU zxs4WH)I7NozQ;I=WLu>!2B;D6O=-qDlLlYEuDjC0|Di~I=Z(EGy zSV;i=7y+AQU8Rd3;K75NQ$cSy3)AnM6;&{lL=s@h7`IsSm`ThqDnpn7%)rp{6=DK+ zaXa$Y`B=2@x!hoqfb8~fIZDV<>>Zr$R)H25x&NtW>V!*AwDAZ%PmJpm6gdAE4XYug z$0pCk`$L4cT@=SyZC{|twW83;iF#qE#pF{u6Sb;-;P*}T2P1vIs!}6{;GTGy>(br+t&aLVuDJ8v?w7kfPi!fN)DYvGeb$IbPG~SDT7E!OV7}abPOTgA>G~i z9rWtG_xF3>@A=-pzGpMfbN1e=_uA{sY|dHh8@nPFs7_R*gUuNIW{3hN{8o5B9(A&i zWN203Z6Dwq%IN!ta(?~zpZB9oRUN3oJui;SN_`TMf|#sLE<0THPX}5dXAH0X&vRu@ zv@t+n^Rva?;*Y$?=&um*fQ~grXF#~z54ym0XQL5=bJ}31`@}9p= zC#jCD^W9;V+`ZqmkCqLt`ziUf64c~Vm%o%l{ z%3rh`1Z{x(?bRJOSf9G?bRdx_{G*}}IYNqDkFj^Am4&owY6wMVV&*-Mac$J`N$_;9 zhc<`imTuweDw4ZI=B}5F>(w8+Nqy6Fv@LO}YQN~)8_8r+RBftI*Wygv;0;wQfDP$r zJpd6Ke(I0jegvU3WzchuPfQYGcOAhkCxYBn-tu;AHafPu0eK{CAv0yJ#2v+z{cR>d zv7)I$bVm3t=;}KV^Bwromi&1t_(A*v@e=lv(}XdRQsLQyxr79(v*4WSav9);j#(iu z=XlMfOsD82`!zmx8uN}w@kZ&VUA+zl!oUMN2+dhCUn3=Qt8l9-ncOIr*F}TZiG#FW z;kgfM;x0hngd1J|dJBau;YW}-fQNYZjUT2(p7l(``#q~K`Wi`&k|Yp>f66#* zhTU~fAkW}}iy)r-;fv?gduX|8Z9OW+_dt^m{I*PPa_LVt{xWK$KJqiG73UdlI~qD_ zXa}jdmxhQH6s6$ii$9r@Dm`zQJip|!r9>kyF>Hw>_8*oNSo~P@23_;L#p+7~OYh`B zeVfReA{ptbL5h6x)L+eeud8Y>xe=0m_I*}trr0z!Eo|@Pc+=-S9lnU5diO4dK5k^; zZq4!5NlosdeE3;aQO)jL&qaN9y&bfA?|5Y;BW=G&WjisgcD%+pyGY6P>~zUw&eL?g zF0DoWl01Ocsdn$E_r3r5`DUmmQx2J9D4&R(+x*xPxKzk}U~2Ph5i+Ubp4>JF$+BS@ zcHLy^-K2K82Q(jE^24 zr6Jx4-1Ute64ts?!(2lEAFEq9_9;~S;XA-ox@qA_1iGM`+{M@C7k+KyJGW-Mbv`nt zRhM!UO9!gI)`kUkmx_MBp%~q{`4Sb*s$1o2=u*9xZGxTfky7GPu@=vx|H9bSMk+cc zb~UZHk5xjC5Z=Er*!NW}WILjL(}VPUdHdF^-kwwD+46NJkL+l%_7c>JxKXE25>Ov0eg30M6sO8dQ1?zX4t&7mR;rF{nozJ% zcfFj)vKo%?@>EUj)>=3sQJ2tnBeB{LM4NKzYeLY!n#Qs3Mr-~)47_@3f!zD6(X zYT;?Mtk2I!)!btCUhl!#uJ(_u2(3R(Wc8?URk+k*j~-=n^D|h zOI~JNTQ?Q2#9j45(-!fl+3fh~=i`xu?l*e^R?N ze5-;0bD~_=Be{C0LymPmtK!NUcd)=-*q*$aMKUH%94W^^$FA7}E3|4ww)@9HXRnO8 z`6-yy+YiRDLJ-eq>hYp!HH-R_iwOEU-&gm@RmRKbI9760l=jFFr{wgXpa^wv0kKm3 zX<1OihP=`!C8HA{Lxro8Q)!GsPkP#>QMar*JUQnheT#z_#P3Gr1NfVwcj_i)bEC=sof5^ zGKuti{k3}0M-58g45i9}eicPPc|ZRTw^1YduFsC16#ra_`g$q9`S>rNurEGUlwZZK zdSAd}ZesyfcnAX2^#*!>Uq8G8cm%3?03RGO7{K1iC`lRtT=6Iv9Q2d{& z+y2iEJe>4zOZ-y}^8aOPQT#UWFRlJ%;oofZ1Fpi!Rr|{_{J%FB;ES{WUdfif+}Za( z39tR$U4Wy9{@cWVWOMwFWkI~S3T?j{&;5Tbr49LC`~E+buDgE~8-KNFo`1W{|EkbG zeB=+|-$ee3IsYl!ANTdC)rWXT;s3JSboYtcgTJEXKf=qkzoO7XPFDJ$!^1JbC1^-$_Ah8hy$c6(nU?A&B(aEj9-*In$U&kD{x&0WyLJfA=Wfy(dvzARSJ+zXMm6w-+3$nWy zxSFM>J6M&!Hs3(TmeNJ*ZD7SOfcaEovLy*axd~{GdgD(+1E~%bB;c> zwtx20oS(lmDqpxJyO)!|tCoK^OUVKizv$G_OL@Nq%1e|7+!41r{L`2}YFXrUCg$WC zCLN!>JbQiKc>**04x3@1`B-J^zt^v@;b~u{$2)=Gdq0=hy=ZD;d zJOvZl4C#W8KXolq<%H=~nb6JppyZnn9dazB@Eh=2+lQ7C4s^ugy##j!NENEcZo;v^ zV@@dlv}yfUta3Ub#H0M&H0}2Q_LYlzdL(WR z-8MBBOG@2PFOT%zjOuB#cFRmJ%NuTE*34WME(Vt$Sohar#3);Hay9kY9hW;?p+2>0 z5V{@U_tKV2_4=u^M1Z~sjJ^cf(XH5O( z`xH)+WPeOYGu=mZ@7@F%)b_vRXbYcTEYPR`XN+u!!I4%QrcL+mYzhlCO!s;#`kUH+>LH;xBL9F1;8Jovg=N{vj;^k`kbd zVU};1Figb@pl$0kya5dw+z;sCC?8kY{;0yQO{(Bd=_nY5So5gmkx=$hpIz}YvRL!l zUO4`NMC`E^z7sZpsL|X(^`t0vmdg|wHT2y>`K-M<+xJJgEy!YGo!LT<8ObYM9n~%K zywnLSt?tn$f8CB%AkL+*yrUNy>ZnEpk1aetdn1v^nE=vOXOIrykb(?Obqjx`0rebw zpCG;;rKB*XK&}vn<qzVV9yyO5CHyF~?!EtJiOQh`6=U2t(p4?j6%JcSi5hWp5?S^l z%lnT++t45W4fF%%?P@W3#$Wk7<_fzb^P_95H%ExAcOrUh%3Yr%2XW<66KQCo8WulPq3a ztKz}IDgL)g4{A6MC930^X9p~lkLR=@UF((`f~1L)Zdmy$x@$VS+e0JKu7}b2oUXRc zr~1|n?V%v(k8*4jJTFf^a`SV;_T1e*iph{k?3NE$aZKgScoWjX+U}QiFJ-qU@(a8& zBelJy8kF4C*V-B)j|Kcfo68X|o6yB{UTHufCoIG|5Flu9`^zUZy+|IogF1p#y{5c) z%2o3cax!I!y}h)p?|#AS#4}gXhh3G6#ac{vn82fiiSo%iIvt-*LBt8bJHCI2D>-z=GmK%eqxUGJ$|7}2)QkvudufmzF_;xj(RavVh)?LmU}66x)X zL2*5rH^U|Mobb0Vs5OR|INQV!5pxWCL#)K`ni^+evY;6;5HXy|tsv2)@b$JQWUM5! zxR`ScekRvITo_&GmS>!Q*9c#I2C-HNakr5u*^7QRYTg&i&C2yr9$sHixIZ($wk<1V z+1uS0W-RX&4O)-dDYi1}q>*mC1*a z4!J4t6bV!8taxP1GteRM6-yS_6|VK;^LsQPe{``0n+<=$w`m>qP#6C5ISqE2lWHhQ z$osG`!FOJlRK4gZ>-vK9>C3hF33+8HvTyWtFRrCBzxQ`YR!Wl@#tRO5j_FTadP_wJ z0|v%u!is&4ABi`eTX{{?ox$l#cIW4{rqy{o>oPVP@fTcd4q3iN%d_f7)kZzXRAiqZyO8dvk znI>J|CyodT*US#_zc0%elG}gd{UIcN#^O!3!Lo{uT5NT!61J`KQb(e4F%*;%_{F(T z+PztO-V>>v9Q1XDB%LDlYm8icW;l!_J&CLer%VpY_kd-)W;mL>6jI06_gRUfn9Z$U`E1akKMG(6#`P~l@ zm7Zzo?}}v)KAWjtyC7@qX}x$46Hx#i2O5P|P~G`t!8`FW%g#)I6^XVitZQXkycQHV zzYoR5LV8@wm;=EV9;lk;QWKFV44Zn-F%apc^9pk~_(9BGE|B=Wq$fc2MH{}!p6?!N zq`Org+KM@AsZDcpQ&3VO(8NM|uW%Ve41Sr4AMmQsV%wwws^$ImNKglKmo(>Xoe-YV_;fLg_ulMr#_h&WCo|B-ocp6&_s6x(}P+GLq-;XG@1wiLu-4cHRK<*kEdsRZU= zwsnatgW?uRyh{QIBg@dHuLJF)O*lDNO}<|sJGdx=luZ`3kp7mXPqT(FbgokUB2-C* z*(k{>;T#zl2`Pad&;lvgDSK6D*R#1B3vN=(Q=dF$_)d8bOomiWB(0iF&%b4`@4#`W|iURe-#)09?GIi+#y z?Z6q*&4Y~I{X@%`ja(Jm98gyc6EMVEr^vL!i^|5WV1#m z!xkPTkBdliYh$FeW58S{C)+B?Mqq8NetH>Ch4c7zkWa8cFEZgroZKxbcS)XuVjGi% zV`RH;S5gYvJZXv?Y4+ij!X|c-eI$edPN4&WEgOcyTv*=L`F_;xZ@Pxw#BNFNkB?=T zAm|;wlnQe}M$yIQ2hG>Avg`6{f~aLdwAce#X)N-^I3XQxD`m6ALTV+?!I8SMAg9Ku zr@73;>VDxGuJL-rBkg+V$Y*-xQ!tK+k~qJ2*Gz@} z&F1CPy7*W!YhLYAph>xM=VTq0f(Y0Tbg}FudSYqN9ML+?l~?a9G)eFb-m1651vBio z!ATn*jdjsYvb+e{3=AsA=_Pb{1tV#DK(nH%&R+cnmvTOcmjPS~X;C6X7KE$GtKrHP zT1C57$>JlyJn)nY;QG|E{r>bwNXs^ojp-*32GwQ zk|!7FZ+vvkUg9>3M7W?e=CA|uK`~zo^p<5n>_8F(hX70oLpmr)=F>bzIDNqL7Rome zf5#{LJ$2R@`7KYMzn(Qa_T`AKPShJ{2{W&CcjEa&7(9MrU_!V9M*XxdF$9ujs=fAM zs*gS$Clw;_hOKp&@e8EcROTiceJ7^d?g6dW*vWGU3+#?EM67qLg;kt{km^G*hZ42c z*)2wygt*lqCB%X$o@hkgaImP-qeeb#`=gL7$Q>See@OE(!ZbI#{I*GsU6J&40oSPH z&-BToQ%9wuL4htrR2Iw=4nO#;pcNcQJg;G>%CV{A8$nJy~0#fhiiS^hGi)*~qr8A&fX%Ml%Fd1RSbCUYQUNP|Sj2+Xqh{VV|4 z+~&H^v$>-4W)_gS55b)LG~ovbmy#uQp@fdvk*~KQyQGE#l``h~5}9!bo5>`m>yp zkGaYX*!x$Qs=sUAgB?c{GY`ckJ{v?n!ALR+I#wVCFAdS2YCfXjW6Hb!!OeXVi}QwT zF(Wn}^mJVU!;>8Ig#ySW$iL&QzI?RL;-hSJx$CE|g!7Al8XqV)iTW zv@#fav6rXK!9d3K0`xFxw#m-3V_3{E^gtfi6d871&+);IR%51tMzU{TRzN~?en`%h z9FK#nr${lNBV!Az%gtFDpvD&A$T@eHce>0emNlNzvrqBqh$Xvj!ajz=@pM3G3{hEO z{@0n#&V(I9=1Edh!lD&FkMz!`m)QE8waSY1E7^V^P>w1frSfW-a&#(~h>x}Hck2P!r&7Z($9QurTBXbK+jY_&r@oMf zE`bo^VqRxsq5dX3OOu@90`A@3j?$E8AHQ-9X$p_GLz3;-f5p?#CYU~hc^7B#4x;V# zeEt--NwVzl&(SOhW>W7u}N+2St|5Xym0k z-fDQ_550rG6fr<32$w_9q%jOZv_<~D`XBGQ64Mwjl;mJURdzAmePyaQNpz>5Sn~MV zv6*|ws=dXB>pS{ZW6Qo5wZ!oTV^veKc>g;hIVHzuDx*=DP-HvoRnnF|SEBVzV8&gD|G|Az zaZ`0b!jDa4{qMbrq%-=UXlQb5yEwhq-cs(t9nxSBLk+v~FB=@GKoJ}I9Im51eHN!I zrlxZ6HDd2$$)HXs3^)8*a?haf5a!9wqaGn*b&kNKO6Z0W*-Y&m+xk9d%jeU||3TYNTkiWiP&ax}qYdCL3p89uvtdf)0!%lZcn{2R z)y*yk2m$}=MME18pals%(qIOm1If&JwJDSm4i51lNiN6-lD^}hvqjQ;wRQK*N{8Id_vkSkXe|KG+sl8 z(mvRH2N&M(yX?pGl86EGU)&eY=S9?Z*X$c$-fis?rb_D&AgQpN7%2%IsyDJ7maRvU$x%zHR} zU0UWAZ&njPDNms5I--?*gSyaA))d0su!-VVj~foq&ye+;7sOJ?1ud7yFO1BN%DRIl_huV`leH4e?8C;F6zBW&{0T+czw-5ZzAPrwBWjiOdZ-zzdKG@{-3iUzxem19b! zw@Xmg0{$VqcgnN<$?Wfxm+0nYSiK4;%7pVk3-hRXq;`O6YKkw6iTyRa`NI5qN;DP~h_tW^tGt#=Q``@e)obV2U1S$U5T5W= z#~~SE9;CDJc`))Z@=RPiTaPJDsgRD?$g5`PpWeBj3Fqn78}3||w)SUVhB!H3xm9%B3m7@G-|uEmmu!beJ% zdentKTz;On;J%5Ld4?z3y`4R#_P5%{`I6i3n|74shBXJl8_F0qF%Iu))l~PDYtGac zRjYf6WisWrZ>42>)L;fMocgC`MU5R~nUN_Lm>tAa5l%se;u0GNVP3x8syrH z2H!$%zt)%_3o{5itU1YGx$q%DfmdlMaZy7j%$tpG^azalYCd12u3DK(LH1@Q-Vg6a zM0bhIS?ce0l}MSVoq-m-%k}mAT_S?%MHt znM;FRZq0?20Q?AP`1(-NfA-_KXcOXHKRBZMHmcCv*r*xhLy)rgM=dy#oQhKO{V`wcC zWwqKz|6~qm@J@!HB12F(BRoGA%PpJ!^i1&aCfO?&`-N%jBi!pO@m)z8$6UP;pFy|xXKq|5 zLls>)W@fC*m^t~sXB$?gXH*_^UZPVTad1R417)~aUshxUIL40qr#%+>*xK01ogV)t zs5~htv2lddQh-yC$wcD-**Vg*u{QX_JqB^T;)1L8O_0xNjPncqae=(rCDV&M40@@< z`NReO=`}4Rfs!?YtxFnjeplj%h@L%xucK*1CS51}Jv@gjhai~mZFeq`}Z;BLR zyE|@@O{W3b{L|MRip;H(4|Q7)cl}BNcul@6AMc;V^biB}U^G20S1G8t=|>|6oznI{ zv9N-yK3;rS8DSMQ?C%bp$#&IbR=x{w)<=A0I`b;mm4=5e4VnC~KJ8*m6q){R+If;d zt^pPx*$9snJu@SY%*+{>nUhGe5l#T}Q!Is6R}Cw^zPTeoqaD@l*UF zd%>>$^$^H_{D9L|K_V=Rf}jw;;kQA_YAI$?Pm-xg3C=E2@g{GR4WWK*m|qNP zx7^G!Ze-J7hG@py2r$RjaF!4qQqUQ@b9a1h`_#SS|EV+cTv4mZ-CTxM>%CJGm5p&}d_f{^JUAd1-XF}3G9|9Z zYrefW%hff!`L#MN%*d1j&34>`Zr+1aR2yY z)_o!Oef@HUDk64=B=_C67N@G9+-$R3pZFu4A#8*Pou>OC=O-*rqX&e&G^}@er-&)X z8CcG}*dd<#ou75~((5(*sl&MT^-@`@H|HMTCKn<(G_N5Q-wSNYA)NZ`R1@md4q<>; zxz-g}HWknM=*M*MVFYRDe6-s&cs^vdOr3>9>6_|l^0N&@Db>xq zZBBO($9Wu2R>I-VSSrZ7EY~?e0IE@DDJQTeQ%6buXvscj;~eAsZq|J-SpIs**RhqZ zwhm&=>dTcnSd`Td&iv>;FLPNdJr)*^va!mpseEFLh7-Do%$@UE7DoZ8xXW~hD0das ziz!G*%f$SrspJ9Qrg#r9cR-_W4a-jGiB?y|>QP_3=|tDDu8{Ay9~?B*nYpPs_{+X4 zn$hgZb@5MJdFHU-W&GwnWVv_Yuj#M5&Pkmc)wacMORsM5^^-9S;Xip0xw@kGLN?^k z_QksK=RRfV`bPPIgi3%A(hwUyme92#C#0;5IFc)$E16s;;8aT1+w>ia3VGCTsks~jz4)hbb8Uv)VZk<5vvqo;9cqes^pNmK`OuBfn8fm(3uKp{k-^f&OcbYE z(;awmrvHT6Yt^85l1zSk{vYQ26(7wZyzr;ynD8Cw{CacB!SY1xi;>1Juh=$oS=PSt zB2AV_a+x6_1_zTd6$@%9^WN7(YB-{6`hq_%6^=)=zFw67#aN_mkDH@xdz#B7X~?UNxzwl%jgxEn8-#;w8JiLhV%pos?+i#Wrw>5pT2q;$ z`EH4oC+LgNm`b0vub6BQI?9{Wx7}mt%yi*rj%-f04>o;GC?2-^ZR7swSj|T^Lo1Vv z5`6}Wh04%*k@`#ydoTst#u(db<-uxG9b4U;_1zZ{VEl8vHNm6&TPPFNccN(12s_{T@JB#0dVOjL+Vu1) z7!PzEsPm-{6!8L88(^w8W1!UmM=QYRAyAF$=M(_=>*5LqD6Iv2e1YGbSEa#-{#*bk zqCZFe;9lWh(%0=UqoHvE4F4wahu=>+f7AL;61Rc#gumtZ8y`UP|0(ZZa{No6E1~{@ z_BZ~Oiql?1Ag{IR@u{h))2#)3BkM_CG1jT-b9w13x9ZY-%`A(heWrlTa@Ug;&dq0N zw@p@84K1ntx4K7R+q7UY zg!}h2D(6J>>2EhtrDk7t+^sju$2Mzsqsra{5MLT^PP&ek;h}|weY?*{gm{uVic4Mk zK5US1j+%M~)@r8+{t_g5+mB9x*YYEs58cOlu1{%u_25*s9-{SzV0x_?YHT8+DfC7R z>#Z9HDLIQB?*usx(73LDTE0BQu?S_e;%)H?YcZhzaA&c)`ZTbT!XvizET+>Jn#6=Q zeU6|}K51^KsCrYnwg!>xtSc}-j_Eu@w3?uNr7l|0ro#{_Yi7c=QG%7GBOwVT>tI_dTUw`inhyICKcj}^ zP5&!yv4~bd`jKez3`=>tHh9h^cY#xp-Pj|k)eUkEkVQV4H)})Wgj=oAn0q(_WWu^} zBG}UW)tKu&amb+4+UXuTS^_pIj~SOi3p1w&pdXE>eyXp-UkbxQofxyVG@_wZ;lL@z zy4>u+hL&zjmgEvKqt=b_vxFvth3p`Q8)#?5aL&`bT|z;s(VJ2*aCb|)=i|IMVria+ z9pQ5O#5^?b#QGWMABd`2y}mGzRO)WMlFi&Vs0MOvTaRG}#}mz)yFSv9eC6xr$NSxIUc73vixguOH!@a;G_tQ0Hc=jUEPsMrTcpo zMrhvj^~trPPTBP&@PS|lC8@PM6#FNMrpmA~ClnHcesjKg%)Oo%?T8W7c9whN^he|v zJVkX}Vw{jC)TpJzv@z)rZ8`(7Ml`%Kj3}lv3JL<;fN-oX+Wc5zQvDN^amCcxVFT@YI>_PGl1sN^d03g2Y=3+`$R;z;2kjsOKWjkAi#!@mLNKkkk(1Bh+;LBb}G z6*FS8x0)@&uy$M`V+zQ;-~8=G z6H&5Zb&Nn7m4`C?tiu?E504ypuVSY6cJwqV2*fu zI7=5>&y+(EK-ES@dQckads6zzPO{Ba0U`CK-&%k(D{8pq1)j+jIsT?g<(I}nki{l1 zA8^8PDTHNou7CI0ThWEtifp@SnGMXm1s2NSSLzw7-8!%noxQrI8!em6iRX34&zihq z>?kW~L-NhQWeKC|O%$6}GOZ>hP6sQOf}_2ro)u!np6K?WgMOm>BUozYeUTc#QwKTF zJGZxqclYd*1eLc$4P{9%!u-EJlXE0{Y+xB*Vl5dzIzZB1ak`f?{=RJ&Ts!Qw{f4Re zMKjU)>Z$O)$oGI)yP@=b`E;^RHI8wmbDm+NO7;6iUj+|C%Ed)2tECmeTH8tjsAU+!1Y@RfRZ%cY? zkX00p4hTlmCpI^Zg_j`v9=DH@8PiP$XAYICCzi-gYZq zePAt>&P4k*t*qbyzFiK8qcm@2=qYW@5$C9_n{-uGP5^DX&WsctOMY{4IVY_rmnRPk z46B0=2d<0*S8Or5pv#kQ>iPsV{ZKnM(!#dgy7Rs&`*bIWoC;U4K=>RC>-Egd-G%qY zQJt9FQ|?lR_EadDL4p}Wc&W21(*4G#U*W(+h1OMTX<+$A5YNcyuVK+aF3WmbHzPe#r(lv?%4Px?M2^Y5lv z$&7zkE@!C`?7lLvKeU{BGOELc?Y^0haEP-!x|Ltap(}aZu zpB8WbpmxJYdqpzrfr(Ef^8G~9hocbOa6m)>lbV_IVDZAvU*Lm%9r?; zNFhq@4PLF;eJYDbD>@8t)GH9ae+7E9cqBurSD zmAlMq0?C?d5Gh5d><2rFxs_;r<#5ZH{=9=`tz9Dz+1L}%K9bmtY1V+&3h!Uu6K~X#r3LnS97r$hart4{u!IEa0EA_3B_`PgeTqCd6Y@I*fV(>O?Mk9Xa$##lpD=o?jdx1Y6w<5+-@^oT)1(%c*Od3(=BcAjax*G=rP zSnVl7&U5 z`RR1*j1Bj=*DvR8W@z>b3J&jCQlaG8-8=3y?vR)6UK;tmc|wFA-e*hL$LbZQ!q`A` zhp?Ln1?GM(FaA+DZ}c|vlel=G`Fx3?|UWt0#cxxPp1JlqrYBQWF6k>&Kpt8fjnawCv62Y!@Upo zBQ0zk9oy&c#UH+4Pl|uM*pwbohEIJ~mJWQsuZYT~;jEVabc<3oo)&IAe^HpY`hce3 z%k#!ZvO*s{1*}V^q)#VL#MM;=>XJyyX&MX2JN1MX#X!vT+6fh~^7!#2vi)jC*WDu7 zLQF3vc-h9MO~vZAnf(Qa*>9@d{bo=MlQc=L?LYqWh9K=LMX+QXUA}{2*M${{M-Bw z)z4}LzCPfDnSO{$9?5Ipli~IR-g_~T-*riBvZLjMPUg?ZYETAcPBjt1GF3eI<{R2x zbbm}Xf=R&2H{8n~pE_VfyF3(roM=LDtOiQ66Vr~Klx4*7L~{F-d4VfZui44nTkWCQ zo>39{oOwtBTUNpL2)uzQ4@wWx>Ti$1TpUqYbQy5SryopFZeES1-;L`&WrBZ_4&G5y z=`NCYGlYmyTF}DPM;Rs|Sm<`UISS5bt*Um3dc_9MS9LjdS1~fOsV^`~GvB>E zh96=vl=1|Q;VSTniRiQ~y?Ux2b}h_LvX!LtRs1f8$()hKhtoVj6vDvMVhc|*N|fPu zjW0&L$*!Gl6?Dd`XJJK9wlVgsPynbmmi{1t+4;na|t}9e6(CKl>~v0ouuB~`%Drc zg$!)AXJeQ@A}O?WeE3ivOL2hG!9+3sHQg#D9 zr?K7qSfVyz$71J2e8JrJKw55zuAF^*ZX3x00omCg@QH2P(E8Y-fB^YsRls_nQ6)Ln zEe{v$Mk*#E?-vYkssnypEZ7KMSjJi$$Jh-d+C!p7(bjbzAj#mHf_%)s`tx+pN;(fu z$5%q$Ghb{?OMrkXh8LTn_y3v(iVhZ#Bd_WoHeL(-P2@4806+{N@cfDua0yhFd=7BBs@n)$Uh(=x|Jv2`ud%C30Ox8N zkn#%sArXKMT>b?8N$HPz{{!{EDg8I~D~tS=;t$+E1^UGiQ25_c{8R8X-%;aOttpK% z&@#TL@FtDuBI2GokI<+-H> zE-E;&RdL66-wwqQ?@{HYj`{+th^IBtYF;)!|`=E3QffEjEU8 zYL$2(WpeTO0pagwLo60*ltCQ%V69$Qgj)86vqk(%(A5kA66&OrX#wW6jc3WsS#_*+ zatjMNiD<`jvTx(J^4N2&&X`zVA*M*OJ3RHRF0Du_7+Ak7TUaj|T~AJ{Bmn#4kidef ztox~UL-n|V(^h2W$ri7%xOyxUc2pX!Na|Xjz473k3?`4Zy6hZ0yF3G4q?r~4*40S9 z(GDu@ONw9xwAG>Hn%AVMl;98JS4+Ty8=>a68aur`^q`i{2?oo zUdLxJw(aArkrhNTReteadG!MkusA{~$N=JLv?^Q4>6vNN# z8u)jHKigX1kQQ8&giMYoBLza5wHjelr(N2fMUV7^oT@(+2JfXsLk>G9Z?^cBiY6@) zpW2BI@UnQ=zTO`{KKYQu>5Bz8CtMH*>@f!xFKuJq7n#~Zoq%c|!!sn-!bpdRMm#`l ztHe5c+ow_zporSFF!RB_RK!7Q7_Am4Ijco!5O}YtLch9C){#(7SVm)SqWeH%->h+w zvU|;-t!A%M?H<%0aCVf}2>)e9Q3RLwrj3LNv1(MOQC%=?Y?hAlUx2QkG*B#Hy~~hy;$eFoa<5s!B4{%4dhB738>Te zk}YBb&dNdw(*F-@Zygj@^ZW}ZB)A31VgUld+29Ed!QE|fSX=_Z9YTQM4vRaB+v4u7 z!Cito!8PzM&-49B-FmC;KeuXXYIn}eoSyEHGoR`1fvK8!;jbjabl}?PaJB2w?n=~+ zq8irQEP~(m>Po=s4&k!Fv3Q2=7~j`kW8)5V)A#)E)vi-hs3DvgVrA({z#PO$l%(_k zIb<2&gkLFb1;rM(q`Afzmk3Zzv#q-${fQM)g8l)rhXWni|D#8nJKe-sE^tZAKW2TY zteS(SAELht>7~_6_@=P2JiY?{5+Vc_4)P@31tLrSECtmw{Qw=Zzc`&3{Je=0EG*k#0%E^8DFD5BqW3vfd=M^o z`p99iI_Xm2h~$#HhDU(|;=2@@HkP5l@?jVvDeCnYfyKUj;Y}t(Hh~Y;^c9$1@QSh> zii!ICW=N!fuKNy5L}1(BtT09c5M06$wC&XW3ajo6xtYh55e{E73x+ZrRFAXq^mS1r zZ&3z8k#AhD3wzF{cFsQ*TmA9=9r}PxAR|{RPXQ>l#cmG}!?r+zi5iI30Ad1hQZ+Y=yI{#{BNs9g=n}Kf*v#;+&UsU)~K&B`1yn5DaTS41xHK5M3 z_yk`$2qE;Cl9m@YxdCK5+wS#=|)=Y5I zBd-6>Q3Wb_|5lplS~shoU}p+U#Dlh^;$*+5EimG76dq)9(~(Wvtk%jvHtrFR{3~Z6 z_ak0uGd)gOg2FH6vL%og^x>ODOS)MGI;O>v^+BSi=NBW^r2(-9e;pd4p z&^(y>6EDeIW~pzem{~dmQj#L6jBYr=-M*os@UN>O@*DkcZ;X+$nh*4}-TUZImO$KV zJcDh>?|sT$#EH#xk}mrWjB-|7hAQ~NpvAX6Y%elf>F2oKJRY{F1^0*>iT$aaK$!Ylb%k=%XJsxGhJUc4-BDg(} zDBSvQqr8u9-EI){Y>pni(ZvDf>~FX51P(DOHFMjaaryG^KbC?DanKt?m=~^wbPF$@6gypAWDsSE>}GqN*{J(opvZsTMhuvT;`5Z{KxA(pdPC7} zJV*3$MY|m292%wLe$IrwBm>2tsCBe&u6!t3tW$yCNLTxL)9M$dhP2>=!v1Zs96PF^ z;n6q0v5Knc0ii?Vj)X`%23R*Ed7m0t)0DIVcw8h|1Ss4j(70I0F!7kh<(P&?lSob>0 zQbTy}{4h`~MozszS8(eAH$xi8JzvD$({1;}Y5JwJwI=Yapu+98VP6`q9uzBQLi zIe`I~G>Iws=YZ6(9&m)f0CpG$X1BdI(FznGlLw|Fj&&4vz@mEA_oo9O+4V15eF5se zz^l>T3+X9se&As4`(~afPeI`D2D5#)o?{^~IuCx?H&n4m4LpyWLlzfdv<8DlJli73 zeBfJGWo{%63Fv-5nF)#+v@hb@5%x1)b~HsoZ?mKNL{4Zy9JD1FJe3Uy%~>}04#WdJ zVgm%=h6tG;;?6vRnp|<9o#ZDvh~ETb=-1S7LZIB|FR2ZVc*+Kol9OM6`tf<{!qfnf z;Z#c+fqw{wG|nNEy@YYSB;%lgKqh!E4e1{22l?a?6#N8qkoDY=dY{V~%VE(HslzYi zvmrdZQ5Tbx{zm{ zq0xEKAaUP(l84^n9{b_^3x!b$b5e+Nk}qGF=cPE9Np5vSs+hXyDo_jh>9`t7{kzNEysy`58O z`RRAj>Z8G09#5kJ+_&c5tJD#0X+nTsM5UU%H0UzI^T> z5OFCXO1^N~L?K5hQW);D@kj_%9;xcP?i3K|g#jXMF}y>jN0Sp5;gc$31%R({_*(RP)?31Q84#k zO_$CrKa7fXPmFCeLx}{zgqnYBYhY6umV&RVYirV6-p%E`k%`>q3nWW~l2cSXe+k}R zfh|a$Cb!kdY8`9lQt{5t{ENdcKF(=vPLpc97O2-LV{%(Q9VBmMV9pP)CH+vFa<+UL zckubfUoPpoYkJSzp=)c9!2hEzX-$)C(cnln!>J?XN?mt2%wyR%SWY@OWKp_9%E50xIEswD-RbN#oLaGCR@nSrDJ$vWAgR zeetEFvt?kS_+n9LE=W~^kQ=8hGmneUgh?O3@I~!?u1qI=egVNQLI|mewkS@>9c`-M z`5f6IESA+D*EtfaZATfN6`YTejR5C zq$m&dxNQvcvz62qhkk&RL8{M^3epa)i=eyMm2VSt2K}s{pSzss-OV&~1&h1FXdq%x zGZlR|q0jX5gt3#Tro%}EcVb6fDS^_I^@Dq*nJIQNOwAGw93`u#0G=kK?_*Kof@hX|KUg+EINmJnwN-% z;V!%oqJX|U7~z&TYat?cM;Ut|H+q*!GNC-G$rmb$tI2Ja8;n;Oh_xjyc%eYQ;(YFgq9Yf}LUs++l})Cn+s~2m) zM!Ul%0=XvC2aOlfB#qbcJ%nEGT4MMCR+5I==_Fjj>)+)HM=xT!-pFVX?WUV{+ed&> z7xo?tPe)zlfz_qMk*yXs@r4UDM4?{z>>o9jsil(Ux$smwP%EX@L$C9k3VBqxS1qKS zP*QGF=e6Cy^t+cVw}*GrME%Cyql}B@aOk4G7fkfwe5+Z3u7pfqDJ)owl#K6=C&?}7 zJ{&N&K*ac^ZIB_Qo(HxWfL}CJ>7*drH0B09-m=V`EgMVxif^+bvHMv8XtFRk-sE7# z19l_4*IiJL#N+6P(@@CS;DWsyNUxx(x?|QxJSNqQ3sb$284u%{h9gEVq(4Fg&D-Pl zCl!YAvQ^%OvKiv+o40-oTFi0UGW)q=JIwQO(^UU(6Vr}dUTvR=7SbG5d77K(x?6!a zWY`;z7phG%%W2)c4m|m!Wq~sk<8Ce>6RXu{2AnP5(+D7)WR#DR<6+s-1<+rJtb$MS z3Kpc9#bg*DSXZ3priD9uVQs}c?|b*3!J^=vm^@1r2FcL7`DY=TMq!c!O{!0zl0Na-OG?E23$3d#WRkL z*&c4P4bF9=@zsi{RyRE+s2)mTLD5kJIvv_pjN)Gh$dzxKBfznp9 z-2#sU(7~Gaz0dA;s7a=V$>sAq3(F{!plP7HOPpoZCcQfl9bkvm#h83T(=zB*(KotfZ}G1Fk_@QYjbIyO?k+t6NzMHmo%x{J>F07CG0;DghjF zpNX)(nw$qG@yGyqb#b#~w#Nm)O6iDn)oeQEV}!m^J^8kCtO40~rTh|>VK$}d>f z1G)*hf}^SRhfG6{!5&r+`8Yqu__gcCQcw|H(Ieq55%k@E8Z@CQe$397Uklez^V;E-8Q-GSmLbI|tA+TviG?`|+oH0zY79(7~^-Ls2i+|{{&2JEL*m@nrcSheZM^~bKP3PE4_czxo z^(!w(L?Bm_TE4SBGP}X~9-?v$NRASA=*l)$&G-Eg8|$$!+kF~7%fZ;fBn}LxoL{zW zkc){Ku{p^~dF~nNFj4V45BE;%5`wtpEbF=DcUV{U3noxjG4#jQDItcz6DJSYR7f?; zJ-__88XW&pJkxHpZc$}Ymkz9Xxf7b&ZyuY707DrJSIQOn9K%b_;6fsWh}-}+gKpBrA`;;2lI;mUX|R1ahX?vyI~g8cs_mxGy}b5x2XHk*IziW%&gJbR$-zWFHm z+TH``*5ciJ0z#UJBXf0A+Yp4$tTlkp_fx>*m0_sOoj32sXL9YEBK5BO8o7W3K8l0cQE@ zW)aXrFCLBWj48HYkZ8(za%UhOFa6EI-CVXmK z9$j`yyAeCtFs6$gEARofKpwWbLSOVw=w^X{sYrKP@8=jzdEvX+mEMJgD=c{9;fqG@ z#8RElN#uirT==ix$I8^1Lwiicvxn?={4%0By#>Tn^m5%!(>U~!E6L~@*Zy=2IHXoS z!FO)i^4XY2lgLN*VEM5oa%v^{41A@CHSV}ox+2}J?lqY?m$lnS0SVyWvQX9yOq1;c8KQ?YF59M3DYhW4%m0XqE51(CnO+ zqyz7U^d1Mi#=-M9pe=T!HLPfXp+K=cbPZtLmQO~&RF}e9UcI|DKHi}F!ld;=VV>W| zY1*IgSrW5F!Y5TbqQa&Ar21_rRJRgDbHv!o*e6j3m*SVbo(b0}=X`ov2#x;vVR~&( z#l8g;F|x19V)N_Q8v~x$lcK}c!91Rx*}Earv#N!o*OX4;Ka2X8_|25Sg&IiXN=95J z;h5ua@J{JXcbDZtQiXXK>xj4iIqMi-d~G!pO20`rdlF!>&(#%o?8SImB+tE@X*KUK10$^7+#b^9B2lMK1pti(Y-`VQG}0N*R{i6GI5vdv4np_a~|B1#2D za>Q5JE-5PL9YSto7)YzM&Lo4T&Ua20{KXZoyNesIM^q?YDVxZ)g^3|cEaVvv5xu>> ze;jYb<*@^OPGfBn)F#I@ER5e_Fp4iy3-wuCx_vlGNG)zS872mJ2%dbkryi&vEMGb- zcjC-3i9BbbWk)H+^@w2y9^1rw%;Xr(hxeA_0$xC|LdOK?*i*P@z-e#31#Y&d%kuY4 zyg{o;5$SC`c1qtnw&j`rdN~l$NXKgQw4S4GChJojq1({4kbSF2)6^>>wh`Q=Oj^0H zOBSQu&^5uNl2QS@s^$5T)h=n!=k1Z|bt%x$M8wt=0NL14Vbs0=W<}$eEkl!sc=z-HAWqm*B3-6vB91!_y_tEUVsv6lx$dRjchHZ_d}FxW&*3 zEdtU@t(?EFg1J|}yf;Z?q5%%7RW)t6ryS|ibh6p#@<3Xpp)!}Jj6{xC`*94hc9&&a zusdouZ$2W(%lrL3L{zmM?a|<_Y$o&jGK5a>ek|MA-oq|jE7QAcf+p6&&_X}Hf3kIO zcD|F1hTp8Y)OK?(>Z*!((rPnBnk=-6KWecjut&2~@3^A5PCzuaRl0`isLw@)g_(U&+Ex)DVx>U+e+~`Z;E;idL)f%r@ePW$RbKxcu`lIKb zV$@V(JHk%;k=s;GGvZR^vg9qHNhiV^2%i6zu&B4k7Pef^QH88zU%NULkC&_A_{WB6 zPMiwcabDc$-n*47SXYjbQFg|0@7Tme!b;mF*q>6CKE&?CKmF17rzqyBg zv<%^fmoJk|I`ZP$Y}`^i{6tpgb)-)rOT{ateX`dIFQ+nnh%OjmjFy+e!l@*pkDMkvxwsXO zB$e1;--9!Y)olBP)`JJv^;fjV6z37_&cBTP0Bm=|3RRB$+@vfr8T)8) zcldJEB!6(s^IVs=X&{#D*oApgc>V4stG(yJ@t58bLwJIL;k9s<)!u+t z?CAbN`X!~lpT2`W<%7dmc7BCsxo)WrSC5LRKO)zv;jP_+nsYp7{}b}Fq{+K}@tk0H zB0T#*e%3TMe2Ieo1%B2F_aXhlAMw8vo?Sb?c=ovt&-I-A?5!A{*YG7gFT5J~@&qpo z&;3u~e-l!NDu8@9xakYWoSe0TC*-y1XZL;a$)~iso4UgAH$Xb8d&MriqQ7n&$l)A3 zeQTpuE}W{RlKaC1S4KyX5xS!yvu3$cyW%;j=q`@mkI*u-^T3$2U-bNT{%;P7rN(zu z`JYYnen?+Rw_}7t#gN?mp2>z9w6K*ccU8i06)oBLWI)fPE5|8JB#=Q&-6qMJME^}m zecKR22Knhal&LQNZ^}De%ZaY+UPyY2;M*ea8QK6voHv3pgY;e*Rud&9pRcdEvQ z4_mofX;y1yFn5tJ8EopUk>>~6eH+!)m-({@*jnr(r%iT{jwb#3!U+gu6w=^#V;%?x zxYzmU2b1@Kq3$0RTZ7x|$+4U`>je``J$|uXfoZ40V&Qw9HMZ`F#x@5QM)56VUF*?3 zOUqa{HF6!_%84)YvF-1fvExu-)}+NrpM1+oKofh_8gGX_yMz#wufB(9<}>~grGGn@ zAW`&cRzvW7(U;7qxa6)KhF9N?qM-v*UCG8e<-zm$`owxo)RIiV`0mnjV*PEfv7fnR zqIk8i8V9qL7H(1@&(SW`NEmh8D}kEftp0aND(7%761YUO0X-8!*wB=Ri!gAewFG$md#Mutfjzp1{%!MfM5!x{(b-2ycvV9M@@0s(CgT*Z7%(UJk=3V7 zwuENAHNDa7LViR$ckQ$$N``SSyQfYUI%%ZMg$m#LgfK%Y*LvktlKdDyM119}*Vp_T z>;ne2^UlhJ*V3(8r8%7cN)4EI{d_(>afARi9Lvd2Kf!RTvj?9p(HCPN=5WUCZ`#bu zl^32OU61gyR$K0$3xMP0xg(va!G?Go19Pb1upeMga~>|fCyETB<6+ zQG(3NoeZX^Gm%B3PYpPVjP(_6QRx;Yogd@ROi2E`<+&rLWzIfzr<5l13XTdSP(ZF% zoH|&s>5dEDNO1lHp=*?{E{nr+* z%t>1%%R`wU`>M@UF=-*ig3;BMB6W?*%Pms=oAK^5~&1DIQVfw&BZ=@_3 zZBd^^4%n-%?MXJuX=I{73%E)u;7kcB;G|OD!Cba*A4=rT!aN(H690zWqt&Ibp&$24 zEa)^Sf&+^Kw%LipNPQ_QMi0;hv{3>k*&9s+r~&g$DquGu)KW30$c_q5k+{acqC*0% zB5@Ck!4A*!3oa#OEk{oP< zmX24u(|f*zSwGT2e7a>!6c4k|{`ly4K}taOrRssA){67(C;CYsqzJ%v>>^^-MV3cQ z_xi1svRUNuqzZT2bnAodj0i3DLZ{TOEDRLm=AJmQo~4fy8hE!`VQ_NRvp$vAerB$j zRZTIUA~|RGtU!sc3zTct!RSp(x6I+~tygn%i7W;;K$syRDcXqL>kx9*c~=E>;Htk= zs!j=>_MK(R@>!$i@zY>UBXyOgY|SsVpq|Q9)MY+tOaj1AglnO|U=Vlir=tj*^2YaM z>A2UvVWMn0m?X|)_~_a+8{7WgxV%-GRs%gfxF1Fz?yzbfR<=T+tg-Zpw+yA5*W)2o zaQS5Jf>|T&UD~)GSP3rbhphN7g@G+g1Xir3>2~BY8GSR3<-TTS8oh3RHrwy$ ziQ^J>BIw#Eu-niZ5q_%6o#f!nX!LoX#LER>2HUx(W(V}(`m}d{HoRTErurZZn8~RR z)3F&>s+rRVx{y>Qf19w&1Le_!T}2nnl9go*GN_bt-6dg@*1w>h<&W)S-IYwv#0l}n z?r0ZG`atWrZ68nPGk?2c;womE!2e=oiCgDbgHKY%am{uU2b zG2c<3tW?5H3rUh6<^OzX>7Hg4%d*vB-U6G3uww5hEiN^9=o-T~3VFk^Yf8KGdn#iz zJ?=RJrfI9Jm5rxra1@Hp9_U<1;a7mxu^N@VzMS4oRLRk-L@9GQCVT+uYRXP}iO% zuhGqnUk`y&zlA6uy5eK&Gg3jEPH>c@^#%4?WkjRx`DvmiU1)PZVh7j+@3hFaFjD~l zGW1EoTRWBoe?as%XVE%F^!c)HNpj+nyy&Ze*@B;a;Iq7FFgOh1^-_Z_rWza+nL-dU*10S=PwB}3eW zvWgEuyK)GOY1Kw?#`y>NJ8X}g zt0~cm-rt4d^jPFduf0Tsrp(Xxl*fy{^&4cTigjginSYlo5JeHD3X=FD^HGBV6;V1% z%x*()t*!Rj1Lkt?bS=x`TS7z3p}l(2keQ#)%dX3RBQP1bh70E-95#o%YiLek>7_$g zrS~R-ZxS`xn#P}^M%NRb$-<8S0k7yc*6Np&3m<&0OK z(@$0tjPyuu*4pk=ZC>YzA@k6Ml+ghGv4NmU|5?fxTI}4%h!T$37 zDU~W&>Dj$O>HAnh{xQWHBk*1lMGDWKXbGrSwQ*Qrwh;0^Y(SF*E^sZ6SCbNts9yR0 z4F4`*8AN?5`tzKcK}|~Mc)BPQak^xd#c8lR-Jp#W`0x_uiX4dBLECG``PFjP?}U+o zF3WVsk$Cbw0Djh>Xb#5t-8hmC8V2Eq72m=>aF_*c74j_FqWn%Gmf%Q%1oec6%h*sBHzr$8Mfa+$`d=$60A0-Y`{Q`vd*33WMVBJVoZc@{4HWGGHWW029 z1(}%?5H&TEl&K#+*m5BmoO{VQPXu9O2fgp}Ly)=^+LQ_`#=?u$x0oO~&GxjlS;ZS- z#N*rar;?$uV#X(vjF@m~UIQ3$7y_gT=xgl>LURp2lv(QGbY+r|Gt}@bL>-3|+txjk+l}z0 zBUmiI<)|t7sz@zM{Xlyv(Kzxiczmzflzn#cXn^@+?Zn&og!W%Um`IU|2x3&uf^Xl5V)V^qWA^8H=mdxQ)0}(4MFji1FkF+ePy$e&7Mxy zXC3D={Zn^5YW2#l%tXhCXjlnoYc4@!Hf%WVP_qLR_6U0qx3+-uFyI5k2))U7gXCaE z)&$*gn-M%trfg-8B&*GtpptF*TjUR9@Ct1bM&j`@Lf-Tv(14^P~eG5AIuf>#Po4vjWEx6H^-96QV*6^P;1nnEir0eB!zT+@G`-7-e_J zj>smfl?9bT3Fj}|Ilj)!0zKJX@4rmTSZbf*p%MA~a#fVeF75W~YgO$ahVu>HR1mnq z;E+QvXEsTCx%zO2ay$^bv|rh0~yBk>{ZTw=_X`8*0Q4Dw*jr>d2PeDxVFe zYh{U10xqyDKe0i#BSMQl^**s2J|};{XqoBdl&*yIOv3JMpHtdcyvJ-w4Eh5m6da_Y zX;)z?CZlPx|8xM=JYG+6dF~|bZQ8(9^vGpV*MR4W-b488R2fec=5X{Vo<~m*wSpe1 zIPL|`_}k}s<2&d~RCi)D&3I@Hu-xvr~nbElyK%fmlk`Mx|K8X^J#k9bc5=RZ&dnq!ZQLJ2&(H_Oe2d&}%@ zUcpxp8KIDXYgfJ@UNsS~>`}YT|L9 zGpawkV9ILD^Q`CrGY(TxYYnON25LZu(2aJv3@f1UXE+>3N- zk|j;I=jx})a~lE}0eb4OY&g~KR75zgUJDpX-jsW^E`!uL^i}end~VL*qjHSvJ5S-B zfoweA2l2kv|deZ#MOX5}Ey!=SEdf-8x9&OWO0(Cfj) zCe7V#iNBVDMy(>G2lOx)tNX2AYHC!8rTRw$b2)zjEioo z$)fISc94u&wZz%8?if(2UT*Ru?NY1MP(py_{5AroY3N{Ip5&xv)|IWZ4#rfjA%6lC zG-2;~SUa%$lDgr80{heTc4)*teMS)H>UZwtrUk%qed$wQZ@4)UtBH zwW(wZ#hBL{Vl$GvH_h2RuxlS^sEUzmJTr%Iz%U^3@hy!#peGImkHA*SuIg*VgRRk4 zsn0gin#It|FN|%)FKd|ROtEx_BFCzH@9yrHuE_Jvn+fBsT~K=G+^RI{M=guu?n?Bq zwduA4KSI1VcoR~bigwH|^-#9B2=OxDHl$mcyK|os#p;CMA6d6z;3Q?3_mO1btR?-Y zVdWFqWU+!CMv6M=%oi_+Ris3PRJ*}W7Y(V+&9|02g&#kC)wi+Qh*wx@N%`2&L+S9C zw){s+7Dh$j(;yQhwku;$GhD?s|N7Bp_}aBusEl~`4x_284Q+R4{zuu6iawf*bgebN zn6`#jEnxI0CoMchY?T%=*;$%UH>XvkSwA_(Q)17Z6Mk!@iv>635UH8?)Fp1r7+Uvu z{O&J*11`r3mn(mJ-`Al^HSq&=YdMCriDmMP6q(6Va*gF3{WIK09erb)zp^u@oDKM& zegpDcyz#>?2k0JYd2I!%6+R7rxPy&RC%b;=938BheEPGw@U>Q<*QD?35qBtcN%xsq z<4!zC6+1Wqms5Lf?;3l~QSEE@ZmfOMw3EQ{!|)DjBG~TqB41nCUexEI)8zpBgUU*C zRb$oq8?fCoJLx51-*zf~zYUEF?6OKUp^VZovE$MGjXLwY;H^+e$xtGl<(uCXzD_ko z;l}6X4zAQ&TLU9zRlmO}18vyfOT+wiSSqhdfgdf_iUI~F?phL4Vu;Y9u4yXq1OCX4z&u#RtAOf#GqE)3PQs`M(N3h z<0z9FLgO#he+V73nkbRL-%Jf?DlpH(!$#V-x0l=8UBoHW{~q|uZc5^w`Mn6_vi))U zizT_M5&<{|UPsmG+Tud*dhmLh*8u3?VUC;Q3Xd=T9jB3$$O+wwca=b58o4a>=r!mb z6v#l$Ywr_41`^mozuJDuP!|XMWc1mGtXW05w1dH`inR(|galwHkvKnlzPJI=7Q@VGXOl&YvNJO5PszjHk8J2$4&{SV9UMieFj6h)@9G!g6#g z{cmS+WfyeLJ|`+}^!sD~bR_2A|8R%7tKjLl{kn{yQH|gUSpyq?&sb*QtV9*v0~?tA zs~M47=}}N3h?XTn4f{n5)5GD*jJU!P*zbduv7ZJdLR<#)8OumlBDg&2E{jMMZh^2B z{AP^mawd^VB#2g_?^X(8XgA^-;T_`{iolLUznM@L-IfBnf#KoJXGg`gW{L=bpCGF8 z@56l7W;_Nc& zMWXmnxUW&*3stu?xHawg5&6k~sd3LO00Gat>%&fplHry>1x9;ko8#ilCGs0yDy4Kj z9JYk*4wX-!us8OiyeG~(8o6%gKNr2yB{!6_r48m8{Su&2c8R|VgHd=`@<@d$=9Z`` zL>1MB80s+6l_1`)FLgBLqiip8GSA96ZW_#ywU%Q$CxeUKlonRpnIVWRkwO2Bw zQ|)KrKM*ti;(Wj0xz4RD5PG9m`6OCc|0DqNuSX@4{+%<%f{ne&WWE16fo68>d%3$3 zU%+aMn6?t8NKEDDY+2?%{1v%)7mU<>&JYUj_hD}4T{Kj)FpCFH%y8q&&T1xTNr@<` z#O^nw?=o&S$eG&S0FdHpxWwL%=`rc`)c)^VlZ71?^-VM3)agHNn3bCYwhI{Kz6A#w zj1YaW5o+6y7cyu-(AFsv3Uv7W=JnD0_KTTb!$rtMHM~)lE<(OyMPF1X6pRq*6g3cJ z0Hbja=Ghxdz+Zoe|HEQ{;(QDH|8##zvlzg*4dVXYE;`Us03jf{f5d0Kre)X z9A3-(fM2DDz>8R+I$Rc4`?Y)t>*vdk2_*EQ`&z)GrGeB%4N}G|K`9=#@OW>wUEkP# zgnMtKS76`H1N*4daodBhjKYoC!TyX0!BP+24&OtDYb!Q#vVG49UoA-E+Ur}hE>UEK zt^Cv99rOgfj5#~*z@tZp{d3+Yceza~gIipKtdeVC1+>a~6ePl9b-}KBa$uW~9@J3Q z3nkzifjFHOb#7V!+=(uCu|05U;7B?>bL(jt7sS+16GS$?OC4^=Et5nT|LXV$4wD;k zO)_c&)}(7~B`&q6-?|Vc3gE%=(WRY|ONI(JEscOeLwRS7&wBOO>4^q6KrU32QY)K? z6o-heK@-3nfz#Jh^9rE(8og}!dho6GYNoZ5^==vql@E%Kl->J^f-kW;gvW>7Wn{vd z%*aDtb4)^%Z_Yd|x48tfVmrIb&)7RR$JV{8R{KlA+%l_XGRmuicMD&kUV}oKswyW~&;wHfuYJYz{K?{D zJ{{uB3AtI)@OLdpacR~B$s{bS#3nFYzQ?un8B-?$aV*$cAK+kjA1?(9V68ta6R2mC<8kz=Hx(J@oQdVZZAC*Z6*N}h+0sCtI2&cdB=`l z)xujU7yFd#PXIzjhA|#|RA$#b@u&YcynM6hP8c(OPzgl zC*G+O@^b;;kFE8VDfNTez_8+#D4yFsm04sa-HX*+t5l^71h>KhrWqLOuGkV zMTPVnJIF69v4tnFQ`M*oVta%kX91UXoH0Z|?dYAPr8G{Wdu6?$vEwJko1ndc! zUHTpQo9Jv4m>kY6aX8oaSHqt4)Xvl@4qt#U7Jh&4soa`xTC8nF1sa{+h&+aq`3L## z7y7QI1>8W{O8hPpegJoC-|XNVtLWFmP*hq_ygrkD=WEjq{jx|?;mXK}2KZgdS&aai zDv#3c^76_W`|~ceQ~MA(acY;Y+FtNnv36E3vP-ae+71iy@`hYmjXl8N7ES4xyD91eHIh{0tTv>Sl-ab`c}g{046;uc15S&xf;Qjv{9Go<6aOX<-=gGyBmhFt z0jq}s1~)x;MW5(hZAfyK^7Hpyoaq@3-$-#bx$F#>e*wL8zy&(B)z_%xhH6uC)>`t2 ztjMZA43ScoPDrl1YQOe&30=Zl)FGc+O@`w8RHigXQC_2+7lr@DM8Ah>6&VzAfE=4> zXom6SQsM+&jO8`F0`#<^dnLf3XH1{Df^=oZLmNuu7U#Wmvmxu)dz;1zjrWxjt*L$i zC-wy%HZ+F)wRp_GxtdZuU$aiLRilV^7u{jV7KdcHF*2e8_DW|lk%2`5Xk&;P?L)Elo~9Fvu>~>z ztE86xjE;%ese<14AN5`9^(X`rn4B|HN|FJv^^OGQzB&kapWO7dg!f5)04)cvbfvXA< zYA*9%-wGosjGEWIZ}^jYK{3@UZ)jh^m_N~F1G0MwBt+rELG0h&In(UNP%;g)VRtxu zRj3)H&tyc45ovxWBY+Uihe3i^Vf+mcD;rc1Om%t@Deu03{3j}SI=For-9U*S&T}S` z-^G6kDx>MbO4fAQcoGz*x13RdrhN<)whCGk0FVxVx)u?%%TPtQ0L4*Ua{Rf;__U=* zQyICnlhT2EBf5f!Fd531d9=1(?>cZ|4AK7}6Z`TjtFGE9d}??NQWW|*dR?zY`N+D- zUq;)WohhPqRBkk#^Z)gA?(s~xdmLYKiQIEriYWTYHkZ*NLUSi8S%zYWtKfGLPT%1X`X_o;=ve0PF5x9aRc5sZKdHf zAJ|qTCbcn%OzIb$4SQXjGe##NU&_y%Lzjc&%#ez-@sZW|-oNF+$d%FAm2Y(cwp5je>gs16#Cb)}Tw4f`Ks8v3QEZbQZIyk76`#8s=O2pAMHL{W-T1nC zLVxIZFAoIv`k;4AUzr&?hL#GCg+xkqcQx`H=~W2ERYCWKQ} zeg-libgC2dX;F8YUmkw8lU!cDO?hzT2D$p0h*>o;H8wOzW#ooLE)ggGa{0({f>Ywk z$`lINvNy{&vo#?oC6%hV-!L6i+RuumoBs=ayc{^Kv3LDX6ZcDrs z-m{m~R}(2Vr>A~qeQQ8hE@vyITs_>_zAF;Fa2d?H@~WU=1TlB zWo_Qc;BKTNdU*Y+!=dSFY8zOuUluV8O&J_KlU|<)LE+IS!IqqY+>0w64wt}w!RUMU zn^{>~9#*%+dY`S0dJyTG712!p%;+ALQES|U&%SNxn?qTGphk+i0^`)Xh~gakKE_;s z#|BX1M`=~(>ygSgiys+mC5v)d*suhdse-88yPsl-zHBZ#DQGk-Ii2x_x0^n6Jh9_> zsSvI+kuU7{k4v^dMd6e***@D$wSH{CsPo{cP$)k!kdO82k!#4*4~MiD>Sw3Y2vI=$-~)h<<0LQ6X#yu+Cm zmln(F4NbI?&dF-%me70G*zH1?i}2}~4@LR#V{4*Acikv=L}0UOokB?CZLpPIPrZ!Z2zeFW8l)%) zJ_&jCJ6r8L11(5bp5QxLM4VA2+q!bt@P*E`ei9?D^|He8!!`4Z{jV$7+bkSsUGrm3xE0%(IRjT{SsG?H zwYEBzN9C=VLRQF%Rhi(8LBSyEoT>EM+eLa<^^ri&DV?(HYyRr-!w8nnUm-u3E3i+tOGb+h)+7;7;}f z;#%T;Ep6rfdO&KVi+$d?N6CrW)wso^`S%(92-8K7s+YeAn2Y$W zdFySKtdG_uUVCXqa5>;XAX`U#uq>#TO;CpG_hBALb+9W5Pex zHJS$&vu{~nCy%LNzVbHP$Hw3}M{~X@-YT20=$N|GrywR`GZr>Qc&j94D2t=H;XZ4y zdgiW11R05nbvaKJ_B@!IeA35(I!-@iH43_-2teG(B=A9GJu~ncCUMlEFYJ1jjmPkH z?R7a_%B!aP;ir95E-K7-EXV3IC&dogFHn1%qlRDw^93y{9gO+>(Ngj-HJOM}@(RKP zbuV!Kz_Houe55A2qr%eD+Z&s!K27>jz6r9pnG}`pFKVUE-5%%2cwL7Cgo1T7e*GDr zIqfW@5mGNd;hJMWo3e9!Pf<9Sv~FOD=N#;kmQvXAIJ(cKsaet+b)^I2q^gT_?PYc7 zFe&h#kdjALf4KuAAxI)8YB_uT3hJgb&OR{x!csPI!NX_l^GIg1_H%U%C+rgyD&XF6 z0cDJ8a_bT^GGyq`_G7s~`Vr6$u)G_}Tq>E{q2=Fm$~&Xod;)ogdftuy$nUuRp9es_ z;a7cg)A<5QT*i1;#@yqR_}usWOFMvj5}&xtuFSZp|4O%db18Or6Rx-z{7(GMZ1%T( zjsN$`sEu#01(5MM{ik9f-37%g z*BA(tO5%&d<}uu8b-%n}#H!zRC?N`rCBHI0KhEyt0hIjXCcWO}#j2Yiw#%OOjVN#$ z`tZ_ERY1vmU!-?a!+KD#v+oih!vx&hIZK6U7T#y@0D#@19o8fWAj5hOfbbnNDzNGa U56q9^_9X>anc10Eokb`76B)1_&D5-5r9v5AN>H;I6?3Tk`qJ z{`TJc?Cu}c{Z@4ysZ(|8>3+Lsf)wN=Fi;6m;o#sfq$EX^;NTD-aB%Sb$OtbjdZN9( zaB#196~3#8!C)|ZdwX_vb}lZiv$M0!%}o{-mgnc^?d@$wMn(Yv0bX8SadGkM>+5gd zzRk_eSy@?KU0o?FD+>z?Z)|LgjEtzMsfmh;YHMq6ZEe-o*2>7p+}+)UgoOC}`|IoL zA0Hpr)YRnV<@NORq@<+G&dwSe8(&^t{`&Q6Z*OmOboBQ2wydm7QBm>m@Nj2m=i%Yu z>FFsrI9N(bN={DB+S>a3{CsI?$-~1VEiG+wa&muv-`w0hGc$8~dRkXk_x}EVd3iZD zHda~icSzli-DJgMtb2BwHou8lg_V#XU zY*bQG3JeTPN=hm&F4ol4^!4>EEG&$Rivxi`uCA`h$;l254v&wIU@$l$A|fg(Dj*=B zt*xz~prELzXn1&dW@aWnKHksIucD$tM@PrP!U6yQ9334E4i4Jd+Q!7hR8>`(nVB^; zHTCxP+Su3x1qF?bjfIDYH#axCySsOGcIxTrm6n#8n3$}tu1-x&O-xK!T3TjgWTd91 z8X6h`fxuf!wNp5_k8o0=UsYTek5=yGl`Tm796S#A0YU`|-@*5-V)9~pz4xy>9PA4!iXEA5P|*L54I!fcZT|_U9_tdFwMb*9B!laDg3~O(YPjFSJGdT=d1Ny zJ?=r1>;Mb%HO-IGQ~!9#0-*S>SO3L{OxXWB9!~i6-}e9F9U0LVJZl3X3YsJ>|7Upr z>ID1#=WzoK^8EMm9V>uhX9yJZuPgsT7gR51hZF8_SOEVC*1u2^(@OviA^T}|SA?Dt zGsJJZL36aw0Eu0wr*VvF+*R}Q`kh(p5Tj6FD>3%VHEJj^9F4jj*c6&5i4SVKCu0;J zs~VDD=L$zQgjmI#BHXIqKUt3FKcm~|RD*JSL6`U}(At5UBqFu#C#KdFig&B8KHQN0 zDTbLw4d=kCa(!AFKBzZYb%+Xg7$U_S6feKA3~)Yf=+F8#FQKfP5nja0#w2*H<30rN;hU*Hy3+8RinUf!A5LTJ9$|8b|e?ygQ6 z7rB-qb51r;`3}ZRs}%kv%}#XmTM(np_Ax^=yi?TUZ&Y)OBE%}2JB;;#QtLdRFUN@I!;kZCVeoz1)s4305C5@uckxCVRVxItJI!u_G>fQ5a>Udnm)3mM#dQ2d7sE$Z1HeBs z_^W9+!2q8$59ra|(E2gU^3Kap@1sB@#oOAb^LX)FL?hmOSniMqdHt<`0rj@C`?WIZ z9S@}0h>(i3P4-TAjbf%=L|E|AR7lL79zwddyBIB$!heBh=zDKa;%UJaU!Z%`9{t;) zklp*%0bsSt-Bsx^RjVnyIp^IVSYVyOIx@3y4f6n09eDMco6-$QdnLyk2OK7_HnFra zepia4EKl^05B8^IY7zYekEChLv&oznj`HHf5?3TBb<8=r#%<;;(2&l6Mb1~7mr>RQY(n>SfH1Kz_A!)q}MO#KbiMWivEur zypSyh@hL?lSuNHt2H?viXfHlw8q9RHd31lR96ctO9|b+D-*NUvGez)be9=-X(lT9_ z3oUstXrH9p*~EAxZaBa{M-*R;-qM z2G<<)A`R3=q`s<_X4mtjy!P$FI$uG;N=k6KrTJ9OC)!Ev0@%r-fmtjw^nH22XVMC7 zQEL1Brm-caf{BY<9F_s}*Woor#n#S48TN+Oz0$+Lz?pVuE1fE~qt;`~Z_?PG1%4hn zDqfLTbjRFMi@1KU)t2Cgm_oc@$nj3|uX zHBDc&o_Med8m4m!;xYAkcJ8%|aih@o@Z3W5P+~(awOQQ! zePO+GR!T&jTuMKv1=o)yirp+d&far|)YR>Rh(!Ouw)|+6a|I*0jiGK)DiCea(fjTN zazVG*K9K+E@N@J7u?q3)f@P18GU3QEuIA?`agV_MPyaEm zvW5DF??*O8dNKxcn@oDxKty4AIa4>LQ(g`8RPt?k5U+v~0N^upd_r{tajFYV%!uTw zG)e+p`8}X?FLVq5M@fXT64I_}Z2#EQ55Lyh)fSlLj)5Ogxyk$EY?u;!FHmjUHr;r6 zIEBoY<9AnGx{UlB8I%IE0)Nk+=A_gvpnklEtqrkjlXY2DU(Pb6Q{CxR z<=KAovJn%b1zZ$1@d~xN~A`h135t`n=lEiJyAcu@lHK&8q4P(L4 zp!BVv;ms(T^Ru{=28xZFAtRu_-^E)Oag>WkNr>av0WXiCsuafcRvkI@*P&;tB)jh1 z*|61qxMUoQrsoG+rQ?$y`0*Hxr&Jfm!ty727Zv6-P;#9i0e=<9DCWko6>A@Q6WtI5 z7=e7D3%nz(rU!UqBs65s#l48^8 z1AGnih5SjVasI#vgyjO=(l0&cEWw!CYly3Fx~@qvl_Y;W+Eyk~shr!PcOJCOALDbn z1L`|6E~~pQIc_-ae|vAnsrnWov!?ACun~62vSC%9F+c-KXa_2L zztkNzv=zVIGX6ZZMCF>42tNIT`v3ItWi&6&{nvpfgRX=VEl#&5(r9{o;H&yO)$ zmAQ@?6zWF=?ruMn6NRSfrLRv<4{mHoUw7`3nljjLkUR{AGfqW;kNM(CoAb+5tz}4c z-AjlG%+W}g`i_Gxb_3%O5W^-MO?LZ-9$rtG?2n#cEk$A=g=Y)@3DVLl zn)pkZHuc`yJd?&sE1^LCN+*I(L6l)5awpz|{AW1rNC38LyoT38Yd=47e>B23()ILa zP@CE=vRgv3=@;=nIqPa;fCMew4Yagas^-l%`S_g%wsTcWt$4n|$3My=;Y?7=m$m*~ z9KScSsc@O@P)Ptmvy^JP^w6zabnVY)-V1Z%21ew%t*-t2 zF+UvMPH&_%Q|MD}0290|YarLQ+9*jfdtf@#1!)z>eRHQnfyQm7+)y!ft-DC>d>raI zDmg+dpf9NIJR(1_#ujtj7A!qeAJ5KPo`mm(#PTF;ha4%OR}YNy@Y~Cmh9IJX^_$1C zt1v*p=Os{~Pe-$|_FtSb@{B(zZ>W~e>evw_h)>z?eto}N>(E_4zVB+q!of~AL}^Gw zVE1aNVcYK{AsUD^%R*gmd@)>oE7GS*bL$x9(NpdD%wXV{jgs2o83I^x5>BpAQW#}@ z?W?Vi74izyppB>wtOn8`1@BPyfgN~Ny#>45JJ8tP^YH>0;J0Y%v;%xI!po2*#Qtk>OR zck@gKsx%Vi)qPcx=RhIZ7_k87`XXq}#Ni}t?Wdg|6xbgnc7`#^^_+$T_*x!vrpp6i zp5Jj|z2?{1K@OxWVbekAS+`I1Z`Wi)_NeS99gmsz;Fmq7=Z?rI?_}X2*6TuD^BB&T z7;a+r8V;W|t`4wgN<|Opw&AATMHUfaTE*fq9e6@-Y%K7KT3?+sQ;CYY=I~GHQuAMgVBP)0?3(c(>gpq*>Rm+Y*EFHG4Mis>21D^J@-& zBQoS-)JyqXb+1-k4F^KkJJ*$t%pV=I`tR3;jUq+jIM_gM>{7m8*G06Cx2!VoZt!|IgcdjzE4uP}$ohmdyF02ms@ zzayIAf(kySrw$f*Cf#N~x5};Wh3+II@zdSb!C~^V6u2P?b?qKZiMDD4b*#O&&#-bw zJVqFI2`TU5=XrO#{AsyktC9X>z>g>XiyJFrMAs}XD6A>DLh+!7oreT;@IaJu_=evw z!GOfaqGRp{cM-WVF((;oBI2<)NLKlrUBIvhpY8o=4Xg{H!CSA)V0+_tb!5QJ+#snT z5{VM&azKz)!Ya~I#}z|`;dA!wXpCgRM)9}|Q}`+Z0R0$4x*~rqgwiI1%YYP>%F?H| zV7}$n(%Ht_PAT77db-xArh+-1xhxsUsDs1ML_DrQZKcdaDcw_+*e_SQt45X1ZLuB5 zFrUAK5V8>h@9`FP4vmj(EcZE8UEd*Ti9VTw50al=du&r+QZr$mN8nHZz;QvmGizwz z?ibiaKM-}%tspY;tg~^B^pTv~PPj_l5;}7jEZ>Nfx8LMf1z>!=n&2j{A`V0ZOs(g> zWLDybsgx@UvLKD`*mj50*>D+w);NJiH_9k03qzwp#qRY%97BO6mfu6$!>ofnzQ1c_ zY{jZW@Sv+q3ccy8XZ6?Xyh%$aTRz*pJL|z5uHG@R097|l{mKz#g}70g>IRCUkKj8i zy3(@_ckQU^3dDFL)t9N_cAz!+Z-lCA$CpKZU75~_*I6=H8+xVyWTw$?P%PGzPh_{9g?Nw(WJ91?Kw~?tJ1~mS4U<@Jai~%@2+?V?gVP#@^KruZ$ z;L3pLjL&wiUL^6@-txBUs0#j^*)Ybjozs%#WgEGNJ1>oymtXY<5HsKdV6|ab0jHRy ze97xU!A#e2vZ9NXT$az!o?PidNf;}94E&xU{D6-g(%#TQy+I7eK|q#5uH%Vcp*F^x zEO5s)u<2cl^t&^T?+nQ-YI#84{+-sAxr4d){0;E*x2p=Pm&`-0mbc+uOna@}I1h^< z^>M5>vPcrx%}95{A!S>Jm}inLrYWzT)6B3)L%&fQIlk96J_$Lh{Z?N{Pw&Zym}&y> z=Jk8}U>N#wAwV=u?Pb7@q=B_5z|i0aPQ&CAKT8gd#$LAwL@50EAN@B1zcvsGm389M z{IooUOH4Z6E3BT%J5e>yy^LEQ1w;qMhU!P2fxSSdUFoMig$F+?U)7_Xk1Xj`x{IYK!P8$j zAtEuLjYY1+t#Z&-O)sX+mBka?66Sxog@oldKM$=owsAP*Iq;8G>|V)!Lk`11#{P`_ zt3OQ=H|sN(;@7;7{J3B7@r$#?8Vhxu6^pa4lyn!5Et_p>oAs{u-HUB+7QAf7k_JAl z_2#FdeHg2jnN1GXk|RB&Bw~VlD*uc8*^C=wzlf+4FlP1 z;w`cS^RozI?V{#r{A4$_DKMKQ z_(Rw{;lb$L^;yw|$4=B5ZCIL=ME2m~>PK}|B9-Y6S*+!p3E*NIB$DCu1Ia;(G|dMd zSaxO%E8>vGGxgovs-e|%EJSFbQ?p>d`5X0jjxw2Xda%gVftjs>=FCl(8i%mn=LCy6 zvAU}oMFxYiR-wgJY;k*9sSdZ0_H8^fwumJ_BL$2XfZZyRmr zCF(i5$s5zHwM*MQzTG+gg8jOtmclVl^}P~V0j7*(&U)KLlaK{Bw)lf`K0cn0R0apS z7fLw8)yia7(|2YgmiI9WKWC}8dgcRK28+o37aZ8~L*@6Xc z9*9OL;a8vm!Pht4BTZ&jD|h($#PnlCtY%OLfh^gweygixGqxc{pA3;yK^@avv-sQBw~Ao+o|!{OeL`llj1 z+ZqaQ2%0?9WcVR)egcUL3+GrLOAA{V>R)h*A(ve0nYa8AP`x=G3e*1xWVgK3r#BHTap_ATs;fSPRDU+fE?OSk6ci4Zc}gsw zPP8D4n|B%#B)4;{omgxk#Kb}~CJlWfmp)J3Ab-$Y<|@Ru-=?$n>@Y@hpi5mDH9BUl zkdIbnPFuzTaE35-HHaOg?AQO}*BES88N$U8iUq zl=qk(Z)%Ectg8#ZSubn)vo@OJ*E-itFW``Xef`g=I9r?|P|nRvmp4(p383Btc(`$(;N?mW!Zbzp z<%lU`_F-*ajgJ@e)yTT1^z@y*2Gq}N$nqd*F1aTbnP({+TAA}n8o+Z_My{zu`m%;^ z=Ju^vQXomgE@3&Br4Ci;!;X-LUv6t9lj;zuKNWfevp1+#fL8rxRk_I86cb10@Yx&m z3#?>xUkDs*Gz1dCBdl@}>Bhe>eiJ>(^%FH_Ytlufm*1>R4L0e_=VXa!QV0($pXRCr zjHab6sPXhPzjks@#|5@!mAj|iDj7M@Ch#r<{SCvie7Xp6ajM#39@S1wLoezbi-Tiu zvPsMCPtyD}J?xW0LHEkB6xJ+XM)0kER zO~HZkBhey{2W__m?iYqe#RNMMS*JROhmGl1y&)6WxW zj@d|h=lnH?Hu%TxUW|&0!cMPW=9GgmiV|Z?8n0H>6o(0|FKepn&&o(h?C}|o*qjob zNSZH4v*M0Y{f@cjEe&glRy(MP0%EPs4By$d&9miW zC}&UU&3qRB9MhWA>=qgzs%{l&Mzxn2}u>WOh2%HuS|G+NaUJe0@XVEHFh6RjdSHgx(7}pHe4Aeyzj<2#Y0zmU59f zO?U7A6y_zfo6ebqL>#kcLdeG)p3q@~D0ep0biR3~Ml0syn`-@ZGC%U29n45a9* z&a6Elhbm{&-(x($b0PvkgEPmNSoT3PGMRSY!Cq>7=BI3OifHRr=ODQ!jL<2QO6jQ2 zsyVA8x<^6kMSQnKsI6x)l+sW|zPO0~wyRX43H6l&M%{gF(Mz6~xQHU_1;1g(^{eu8+8oTO zK0a2@?O74`)UN|;>iMhK)8p2a%Yqj&u?9!}GK1}0_`E$XYl=M%Xifxus(H$}^!Ibf z)4kt-e;y-u1t=5_eOH`MJH$z3-FD^8+?*3qqi!G|%1}GHOB$|UOd*`-V6suF z2p|9Q1z{;yV9p+!I^#$)1(_l;lnWF3wdhCpn8BxaavY@F+?OWNjlcy|0jqjnc^(wi zIW)L`#+$Q2gd6@SRrG`YTJdpOgq1W)W1T*Zk1Ek)Zb zewc#?sd@R)a>3qjkFt6HRz8gLo`+BIu}9eE#!Qs#C!&K_YA(5vY6F&X&Q-YStsUEI z>~DWG3CV&|aaQ{cKKXxbc+wT)QCV<>0rB*Ezf;FljFOOHd^bMqIeen?RJt3 z{V(16|3aJoL%05$M0Nfi(Dk2h0sc+IO8up5U%1-;q2qt4y^ys3<#_*1#ESk;j&~pE zW2aeY#1V9oh4X%r~)i-ngA(gek&B>mh*No6skm zf%QOhCQ2`LG|*5m3Us#z$k9W#f7Q-aC;bn7{9kJMZ|yevzr67zk^kuxw}>W7WD*ta z2^A;-4f=GnOIW|+=H6J#h3}=tg|7ZYUKhY+SEj>U>i5AntyF8j%*Onki2)3nj{eKH zsmOzYsDl3o?9!lAe04`(l8^iD219G#y^7Ce$;^eJ9QwwiZ7yJEW}OIs*6`MryI@_H zrHx|!Ug?(leWFA}@ulNC&2q*6Ad-cag!9zlrtirS;?OxdQ(A%BV-8}A<@ZZELwZGC-TGv!&ROz8ygJ}%e(oIc$ zg&=sX&{*xov3a*RC)LM93{^w-B!O@m(<_LEsV8bbP*Wj}T+9l@L}{c_Bf~e8*Zoy{ zL^!*f?^W2S<2NeCgj_w%JZw_4%s zwgXO@J5phtm3n##!ME2!?K6@lj_96;GJ`-~&r0W2-LY1HaYL6Z$4n=?9>z2`uGPUQMRj2^3)sFXSYI$RtujpAvgfywA&rSW|D?g4Yj5#F3LF0k&ptf=lW6>gL%JFfZI;xS_i_c>o3<Uvjh4NQbNuIXpJM1%&D4W&51z3W5ZVtQbD-P>n5|!I$NVTJFlTbY^4HhW^@u zTA0LeGAuPr+%IBvxi%Tg;Uv;_+Dq!U;nxLNxey7cl~2^58ljj)kFeG`3s+vx;tn%= zC|~%=Dh~JiGEt;Vyy8Ar;nn=X_8laIcyk+HkjOoBMYJ|xk9z%jgiVLx{A|)vo`PRL zD4J${`O`E*J6G^qkIhD-VwXhFrRzOF58oU;M>4GlI(BbO;|GnlumA71etkNNrEic99= zZxm0+kP$P`K)9Q%*3%?!H@-Lb@tNtGyqy`b6W{6Gvnu`2chObxF5_u9f}>M?>;cfUP0aeHo(jNJUF?S+(tbHv#UKZxv5S9O872&%-wnk#@Z7M$B_&;_(;yULvS z{u>#3hv*t&;6hfxVPXq0YY#vDBqEPqQRew!WTB_GN3~VfFys%N8pltj-`5p1`X+1! zZAt3K-fCP8+F>1DA3$_?<1)99huOIJ28M2YU+zAOV!|=2OWzT0o8evR-Nc!>NaWv= z1_ZbvRS5R&O}~qQglERORrzpw;|zXS?h;`^<%tU=`8z@Vw~Hg@KkNEUiMOaWo_^<~ z7;N0L2xGsbo(=qJhf?zRE1g#-uZ4DMWA1FZ!&mh03H8y=R({`X*YPN|<*`TA^@(tL z{Gv0RC@}3+`0ja#Eg&N{OGsiSH%#`bE+0Sj!@0|@FoO|>gpI6-zAN5`8Rflg|_3*_Ofm~w1F{)9IWgabABq;pX*mec2=4!*SIkK4qEiB0SIW zWX0IgnYi7uc8V0UL^bSsn>H`B;oLoBmp2IVerg)H%71W=df6IYTK~N~IUEa3xpnej zr=*!Y%-VH-@KDaFTkCj&1;$%))BNnCxXN9&em!B!mI8w}!v{)6Tg?uC(X6IkIdnlx z;1y`>^59I963MzDcVf z2tWR6Ca17da$0;N$h-gwvC5jc70u>;_WV30?Lch#w5wK~Sfj*H-W1A)DdBM@qk}dG zmr2t=)R75{g`5Y6l1!rN(3ju_xu2buPlifrJLr#d`sRdE_H#WG%qdgadr{pMsA!EV(GB=61-Q1&}5fb~&xD%)BJvo?2+$9Z= zpx|?NXl9g*qj~kNH^+5{3oSd7w!He^`T)!P!Rt+2Rh`@YJ5`gdN z5Qw@sUI9=6n!BG8c_$~aQrLqkQN~0Yl(R}tAC^~m(s;@e5DZAPv|VYR_dc3F2aXKM z$tG#3^lkNg#%Secu(P~3?)h~gUNR*mAGWVs*SlK#s7AlP93E?-n#-e18pg<&_oLfZ z`-kD~k3*X+1>(M(t|(E(z@m^0r4~Jt49QpNqRqVrFO==eVp{OeISV)N=$gL4p8%E+ z`Lgg>KJ}I#$p@alPf`(_|I_sbsIR3sI@>nq-HIt;@O1O>pqp&v}? z1wPqbZHp-7mosHJi0Zuq-~FoEKh$oD!ia2n1N#G?#Wn%d)I`lVmC+Li2&Ui9O{p`{ z*=*#j49H|Y_qml`QTZtVz7;aPoL{uP0P^Vs7|u6v(O>ZP8yF?)#o|A*xKy*=OqgEI!_HD_|O6c6=q&B@Sro!vPx9xtnh{gSrju+V@kgiGEy4D_w`4+YKd(FMU z%k5WD!tI(1!ZD`*s@7J;U){bPPLz{1qvfOVx`cLV5+yZM#+DL@nE>h&bN6>c*Om*Q{u>}i*JWY!RrRjrQB zq~+V`E@R?al~yny%N$Y)2a-K4YON`Zmw0e&sHgxQOU^BbBC{^)J=5~WSwHEzY)9l- zfAWKk>OfITWvszB1S8)VG)PzYOUj9sMm%-1?Sj!$<7S;qyu&yjqhydx;O?}v3D7&g zu$g5J1_>vRldzllOgVr}B#-ysfm`u(9S$6}N8 zR8seCtShY$f>tSCz~`W^0iEvq5aq6KLQmqKr)BtlNaK!%VSQDQejIkb(`;D;=l7}d=AH>?zpP%SajsEO7##_k=;MM ze@j;rzKk?#wgAH<98q$%5Duv6GZ}i9*}GRPlb{1OnDAok3=-~zVGMnvb| zk|iUt81NsY?CcSeLatm~Lm_CgP0meP`CX;icE<6k_|nE_EyD1lrkpPcXf_&kuq!Rl zyzI#k(JgWko{}Zb+WR^-AxS{i?2sWiK;;yqD7U&-%vHCjvl=@_{jKoS>ZgDj^-E6U zp#YfHXyjhDg1($Jx0%{>c5>Y2 zw{YsF%ZKu!Roe2)b@93-^?~S7!-JI9tdBb~p$12UM8u+ZiO&hyH%opNxT)id>U?p_ ziw@*pNoslFp`tD}b#{wUJc12PvfmJly#6R;;^bDM^3gDD4mj_e)hkwRDW!41)XeVQ zt&l~*JY236Yw+;V1366i_a%`i_AWU)XLj!NoT+$WNM})94|0 zGtf-8d->EdWn0UX?L0;jcUM>D)aAIJ(bKYE5#Pq49=QB9!ZO81;>bZ}*Hg-U$0O+= z=u`S#zToV9I=a2AE{b9{T2fxM33Ivp0%+aSp5CrbS7ADTR_qjP zW!~vp(JNYHY#iKV2!>Hl1N-obS7vW0A3*!cem{_(O-`3HlKZRRmiJv?u74&8-o?Uw zb^C%$9q=(*p%CGXTR-by1*&fu!ihdTb_WfwWgMH}tFrh^w2Q9JJ=)q^{kb9spyhMD zKs}k1D`r`dW*t8iYk7Qmv~}eM)L39A*~e%46`gl-QVa9f*z!A?sh0g?O~dd*fK2pV zZ}U0Ye35uy^SRnr-td~ALg;1ehf(;J1pI~5X1`&sm)U4*_#dtfB~EjfQ4R>Y?H#$L zHIEqS$=ZB@!Z&IuM>o+uEc7Id1*RiABeUpWDyzK}qGcq8#Je($xON|rgkd9xh0-{r zY51&Dxzv&(F)WZT*WwY;Foin&UN22w>1>~S@#ylm>Oj+S=wz0u`Z|>`h&kBuDG`fg zyp*(!{HEHE%(uwyr)nt9zLlb#3N6cCL~wK14K>z@M`$g&0&+5T4~((Qj-#h`d`?hI zs>Jy<=F;c%=bs(*8zPs1X+fc^S#~JiSL8bCg&W04?ti|+AU)-9qZ{N{wy(H%)ALTH z9*9}pVeH_vuk*o79a8VgWhSoDrmLZF;vcDF@~708Q@FIP!~riq`f;x<2y)*IymEtk zuK|xCMBYSqg~b8s;eg|){RS0Dw=7g6o9K`{E9s+`gbL;c3E5e;mf3k^2cGMNh|FB( zfefWFg)!Bf4a53Z?_$D=#_K=WA&5#sTf*ROROUr@In-yx$BCC6>2!lJg5OS424Csw z0FrI#q@b3#Qp3V5qGE#D5gm%?fn9?1$>mHV25-~7&AIm%6E49y=Dt5Bd_VOOj84u` zP5mH%lLa_z0=KoW>yYi7!9!QgF0t#}-4B;*B)r2f^ORf(GB+B)>XO&6YV6GbjIcHQ&;xCam*kJj<#d)1 z@%4r7j&k-XyymCzT)WJA&}8_ULSUAwc)1a}ZIPJHqW*a3=UE^M*Z_Cx+)u=h?t$<1 zJ(6(C7UJn|?bl^~kd13+&q?(L)1+~E$7qPQA4c2AuWiI-z!1!}FGO5_$#u3bXVQ8~qEMpYpG0_W% z+Orm~kM#-bgyh^2^xD@{v=Sn-uT2vD#mz#G#2hAKRfwxlVSJ_Um+^XC#*@WdL-jY;>S>Dmen`efX>5WGyOo!E z5ah3v*&0x>tC>~H){m>)2ayxJ=oB%cOP}AY`;kmEE0*I~A-eL1X)KZjAWj>_`g4N>dx#ROxsKV(6%+u|Nk zLtfsr)g12|R8A;+DMxiKeeuYkW$yHSNpWY2_Yz#dH3GXnIlE2fqmxEQgh8zvM#`pm zEvdXnNU&o(a!i-GPJE&-8vez~)D^Ce_{+*0A~&Z$N6h#@G)P=c$GuYqZ+Zw=CSg8& zKRm{SaO`fVvLd*PrOTfD)odfhH_R(Jcc8Jze0|56H575pdb5R7mFkgB6#z;YeU&pN zJK)brT8wvM@W4+0W9l|rm0o|ae-`@1RVTYmSLj1BTLXYYNk>2hFX$y-sor)ZNmSkq z;b_^8fOu8>dq#T9&IxH83*G^|`e0?M-5otOvFR}C z9MCQ9uOnd1e;=+){=%2zU-!XD^3qTbJl|oH#H25@E)@{6LOZKjBn)}&a6Qk}Xj!2l z+UK_ix~t(LB1IulmjYR6MP-d6&C9dRdch(Ib>V>fulE9G&^jDN-ek7&^nAyeo#xGB z!>(lAIHX#_<1puH7hODto}z<_@Zvkmtx3Dz1HDXpCpZgyF_T-NI+a@OkeGBHBbD4zA04ViA))fg2+LB8>*zt=1_v|>=wrLa;w{Y zm>^rb!RQ_tr1pbTax#m~yjruUCYz{~h-%*|)+DaQW=~-vqvi0=d?1=_-O6J-X3DK% z)Onv#TM?vs$bY28w({t09%ancBLR5sWNs+U)oo&;NR?PT&0Wz?#><4gt;1(Ju-}wk zMs*}F+}U7j(ka=u^x*40s;NwdLF2T)Je37*TmEiLF8)0ABRyw>NEvmUoI-1I-fax< z@iRECdr@z@ z%NN!CivPLjWv2oO!lvAg=cC%!ZvMS^oRIg9d=R_(jd|CvCmkhmTSD5LNzP^;i^WlS zFhqIr2Y2D1)l!TlURm7Cq_5QD{z63Euu=5T(_pw>)X^1eoNyrWlSg-PnE;cN#1X6W zUMzW*zGU(&QOX%B*#2F1itDT! z7hAY+Mw6`FCVv3PQpw>dJjp5|I+ckytD6pEKcuY{*UNq?XmxrHqLtaW9kAiIb-0JG zX=zH4nEE*R@ss!G$#3==uU}&mD|Des6Bsjx5N79m41PtJR@l%z7ryZ+d{mT8NS-rE z$hP>T8Bp?TbT}FI^l=_6x5B%dK34R5N}UMUkO?>nBv(YRSH7G^wF{~)F0#F8p=DYZ z4W=2YUuL$&(7QGL*bs2bsN}=%3HR)}`q`vJ>d3`+!zDw~h5O)XmLcTcW;2UI?8~CA z5s<33^J}xe8&*lsQH0d>i(|pR?|LUN{D$id~osalFcTV-f;;1)DYic!tP1WcA4^8MG=C< z)zGz0Lh*{pVP28osvNKRKchc2S! zot%!b5uBY@yln}+!AT$yE+ss5^A;iF@tHPq;7v$pQWkJ56PYW#`llCH|C6z{sm#kG zIsDl#_%p7A_V+_VRwaft)cJ_b>Srz;dB*b$G~0K@fu!At1YsSXx0+jFT`k;b1fboI z)33$mrq3-pfquPT!aGy6Oo$;_jJyyEcqoc@w!|4n)axpz!gj(I3DqW-SG?@PC{zEEj?uSTEN`dc>xFO@j<*E-qB= zRbF*7qG(MN28Sto!qYh6k^YJdzSe6_(?!?bQEdexmUt9eN=DcPi}HSWc^mb#wnup; zMoU{G%Yy<4t)Y38BYYza?Y_@*2u=M}ZczN{glYyt2mv=RixOySs55kkwfTlO{4ils z3{*V}h1c7_c=(L_h++J8Zr6gKG7GfVH|OARr>C6qF1|GEtfy4C2zZ;|ZTB8tEG2dU zCuk4=CKruZha$#w&?)4i`>(}n#9SoFB1Ag)Ke~AG3Ouz>PYbVNl zMp|T#x)Z<&c1(w~_evI?&Qgze*E|xz1WhH5zv}XGfM;66F^)RC%ev=#+0Hq_!`C*Z zH=mzY`sY?Tk)UE1&12o_*+h==mLP_}Qif^XEIrEMSrXmqzC8KdhKn81Yklq>`%ug= zk)lJ2I#-mRtiv?cAL>eq-LX(jB-f}@PJDCzuK*|o*ZL2|mmk~MGQOQgr0vjeM=_D$ zqU3byKTFyh|H7=7RH@t$PJBl(T?{D>e2;*6oc(}ei;eF&D5t{H=eQ~r(zk7Q=HWNA zqaY0h6sIC)_L_`umJ$+`GM~~|A)XSxmaX$r_Bio%oyExHz}NP#!&-V84b^Z>i0|Nl z17F+~f(^;Fukhn20vL^JAWH~$Bl6NnLwcfjQDb5oWLApDP`Xj=ixj-2gu=kr9DE<; z58BlT-R?TFQ%3ckemU&C^X5c~%!^dnV54mi z)3-r)EN_l}u1Kks)rKwWsvF~5ze#+HiWuLI$M*>gy`-p`4RaBn2l376eg+5ZqbrY^ zWt`~3Y#hEqIXQhF9QcYgcr`msXEN_$DLRTK+uL(?x3_0|)XYGQ%N?>>_TG;6eh;T@ zcV;24`zgk-F@Sr|y;%3&g5eiELl2YjJr991S&)yZUV*PnL40jn%>biLd}HeIA#mVp z`)7<*^t8W%_V!Y-2ku20G4@$4?;q>!R+b&}H5<4M3xU63(%XE->#7z_vSI&2SfpKP zJ@mHaBiSd4*3$%3MHF%9J zLeEoP0*%nNWnOh-eA9mRn+D(bIL!Ed2)=X6bQ7uet=b9jR=Y`=06n5u&H!Z5SY2K^s`gSuN2EIN2eb=>h4e@MHnFD3*SMmG@EzbCMXoPM@ox8w#C7 z6}mCOCgQXnZo8|EZvnne$?0T%+~CDK^e2yYkV$|}awBvQ-^rxZJ@+es^T`fJ==fK| z*BJcBSt+kmjc;}hYCTqITspqyJ+LOSr2t&}wEUnCYk1NQ0PT#j0^nQi(9D~KC75x#Rk-hDnl(t%o3OqFHNn^`?Jfr#p{S#HQ@ed}|R-}9KqttlbEHs*7 zoDKLYb$`vzp10L)IKvY#+R~9~@buGoTCJ+ZcxPm^BlJNITdE%E)=P@CT@xo#c*JG+ zhqgzhnH|ekx~nGR`@cRo+Iz~PcQ51nDe;X*^p3IATA%*`kG_xT+1eMa2_wb-EGitn z+BYLhH|5X3`@^X@-$X;&(@zIc46l%CILycd(@ z#_3IUIqnC!69e&GSFHb2;rsDqGQL~Fm&Iay8DAFT%lI4 diff --git a/doc/user/project/clusters/serverless/img/sam-api-endpoint.png b/doc/user/project/clusters/serverless/img/sam-api-endpoint.png deleted file mode 100644 index 3407b2684fdb8daa14e043cacae96f9f7e1dc2c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29991 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 XaQdyOTC}tFue+h7srW(OGU9&#LuD#8 diff --git a/doc/user/project/clusters/serverless/img/serverless-page_v14_0.png b/doc/user/project/clusters/serverless/img/serverless-page_v14_0.png deleted file mode 100644 index f88eb4bdcd205825941616c5fc88a97194f9027d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18188 zcmb5WbyOYCwl3OhvGCyTPH=a3_uvF~CwPE_g}Xxt?(QCf6P)0|g1dWgd-?5s?mhe7 zcitbbMt9e&s`-62YgTu4^%!$>gqn&h3KAg_001cRa#9)q0QoBdiwH1(uMr*f(tk6E zwYai40Mx}HKbgY*ErZ-NWF>&=apHr&1wIilKfvP?)Jiz zy2@(glFqJ{{{{bB5~j3qcXttBW%cs%V)5c+adx$4 zW#{MTXJzAH<=|lctHJE{(aGJ^o7u^Y>fa>)i$}`R&D_=2#ogA~iTod4Q!{4|cVSA( ze>(c#>ECv`+gkm1Pfl+CHLSk_Wc{awm7Rr+^?!m{dfWbg!2W6ZH|$^I`geCi|Bwl& zy4qU)HTWO1MA(J?)xrN!_uu9T{lg-lX6tR~petqTXzAqk4~Bz_hewF@e{A|6p?d!f zWoP5&`%mb9H2oJ;i1i;&{fFoNJuUyL{W~urNJ6atdyYkrN;p6F000c2rlcwT`uci# zd3k$#dwFqra&mHTaPau}`0()X^73+j|M2wud~tDcef{V8`T6gS^7{I6e|Put7xVJ+ zcz^%$kM#Wf`g(SDcJuQ3kM`rk%k$&j-rnTI#NGYF!}II;{a>ZOY|m?J>&M5(hd0j; zFR#D0E;mkY9v+?^Z_dvzF8-l-JORPb10cM`gPV(;+1JG~GMC*q`s#agJKsJDupRdHU;>tIhPv zuFI?I;-l2DwyNaW`#+I6LLM-`!(ck-<=T9jO6W7-_1(i)Jv*WR!U97w!p_cO69$%-I zcRnT6vC%PJpB>~RM`ZdNT1OQHc)2tbBtjDlT;4IPPJVYZ)KWqK34j5&=n6RfU=nIZ zFpuu)NTY|Lgp{=GKxM+|!A@i6aI&4;*FbywfMiB;qPdO&E7iBPO>G)H0Ej{xKvG>? z*>I1_$qRQ7lG7Kp55GLwtuM*^`J**J3rmk0fZ??M*_im*nAKSRjm762OM4eJW9RaW z7&gl=%Xd<~VaeUOz7R>XnbAH10uo(0p{huy&W17-e$GH=<0xaE=+wfG{^6qRG_>;8 zMs~h*+FojwetCY1=x`tk4n6*?ZZ0_n*nW z`H8HN-G(6iYbpM}FSLNTx4U!u`D;Dgg*>c3a$P>%i)~Pt~vIpA4L+S)NkI?dFLpd|3Pg>qt$SI zrOoPNz4)@bcuuvA0u4*oRIpoi+6EQ%$<5OnryAa?t988-E+=C5`3_K?bmS&InbJoj z&k%~g+--3k4vQ@eOpl%}oqYfB>^AKk#d%hppDJh|grgAnS|}uP8j|~2PGpOLvOSJZ zgcm`)C9UhBWLzhj7rvATKp@q4x2cO5CzjTmKm0M%DtJ6oD2n4QidMPEc7wAZ6;nFo zf>CnQy>P{!K}OKhX?xoi+e0L{OYhkv>kFM~E1f5t$ImNEsI>&Rj&|}W2oYN_qKlm> z`7?P&fi?ZcQi*1QD`ys+15b@XLAdhEnEb8ikZLeH(IrG;u$3x-m#8Kb5+Bkzq~vM)5fW4CNKN zGE_;Gm-HQkbF{_bivP0fxkK6%+joT47(esH%F(_g+bZeff*n~|B}<*|CfuLEuS=9BCyuhjJ z2x-J*?*c2?>lhr(Pu$N>&3QM;=Favt4*Ab%CnP#5qZO=2KB1dq*C7wjsf=ZNc8-?y z4IgbIm&9k6D%^cXiA~3U2E!Im=?Pj46E%g3!I3N9u8SMiWxRzxFh2~OXwlpAXnfw? zZ69d1^sU@!;24;5!uJ>({V+dSRsW@SgXgfdccM5jPQ*d< zvdwFskm%vvEPr#bN~g2XAn{(yv&{<8@#x$oTQ-pXm1<`pCq}q(ZrPo+_#o^lwVch? z!fzj7NQi_-qak4~oOK+PTXN4ljKw-xll|4CzVs~w)q(nOm2}EY@08xHPdQzAesy~4 zaFn)a!pM%#Cm<&}Zv$zS!nnaU=-EWA&S0k3Pn~i4vd5dD*#`@lU`Y0Vzs;=?+LOFD zo*z9<6G4M0lH2eBKNg^M{J797S31~6k-DtuiBkW3CsspXO6p)0km^|W$w5lAE4wLi4AmP zyPXcdcQ~XIuefHbCZ__duvCG>L;$G+vD;FCaG!~2IN!d6%BE?Q+V0+@+sflSN#-OX z3!Bixawc?~#;$;QvT>y~AmH(`2jUOjSEk{Ab(gVbP~O=SSUynz5492%jIZsZWrOSG_YxI6$|CxG@BG=(aXrBb`EaVB=w1s*aaLvr6?iB+Wo zoh`|igsK;$&`N4xWNXF$Q%xdZRLGJVixkWMS83q5aAiV$k{VD2Mh7u%dYZ2v7@4I! zNMA+l?o!E~$Yw_PBYiGmd+!3lttn6V&zaCTZIX-k0rnpKWsj zk|E?8FKKm3=F@MT2jUme)hm@Se0%^hDYa}<+d;grtHZ?#k`&IH_kJ^ScMRy{lUfs` z0#b!*+DTqF%{}+?NF#fno@lcnp*nPKfU=1Xu12Vu*}Y*%-(S(K*zWD4jL-K}_C)m|Bj0|lVDiKa1BXs*K zZ{G?!CpTSCgf^5`GG{#;-lL@GA_C}As7Uo{J9IXQ?=Q~|AghE?d8=y4mS>jvgy|f% zB|`Odds9;~nx*>rg~w9L*M>B<$&Q*1=dR{OEY~(qW_OFs=K+Bhx*Vn*H;Fyjf_A= zgyr16w`ZY>htJ~D!-x7JG3VRPGJfKP~0s~;p{-J+m0R;oyQaHRQ zS;Rm$H90^F0{tiCzvXp7sq+rNL_+2pYr8p_FHdHn9ELwN^vr^aOobC!V5aE)jtZot z^dXP>@(yo{Gzo@Fv$l9~W!*?|9~MyR8}Jus7*_D+t&Jb_fXRmwc_eVtG9v`mjgK>Z z)!td-cB7rd7WY0oVI^{X$x^6|f8&TiUjOBm2?WtgmCdEx#2Ui&SXS2!mX@Ya!+YP4 zQ)}ZjI~_RjJ2G~bVV|KhLHvRn8{+4Vb8@=BFCt?-(SaCVr8QWt$=@X^;d++p=6ve6 zcbnw~)1HZ4t9CK<1SQ@xUHDmD$|%?%2S6?oZ(a+&rZJkpqJ2jrhnWeC{IZ#{K@9|Y ze?&q5v;G9X!D3RWN?=G6=pBI)e_n7O$9K`5S&b>)-Uo-T&~+?{{=5Jrze1+`^PKMy z1IZEoe;u3FQQKO9|ABjk4>t!L%&{56ssgboo0|J8$tp+gX%J4@rOp=0SnE(7;!+-XxE3 zJX%GgzDZR{Anqzmnj@XOFDi3|ST5&*ICaS1Lip2=F2P&>%%Q|Rdcft*=9~)GpY(W5 zTV&XhjJjx8_UWgNSN4zR$d+uA6kj!tD^afAquzo>>yl_+DedK!j3glg(9nHt`4@f- zb%S1*5m_@FwOiHoqGDJFkw3=?*Fsw~x1;5cMM{(jqvTFKgTGJF$P?5A#r|l$_-ha( z|8D!0^5SD_wl5Vmn$Z=_UG>7`d3OA5D1U%XUAfNDs2jTBwi6Qb4|Wbz zA&I7y-F4rbz7EdB&6yAy1|vKE^u{H^L~APG?bl{`V^P(6VZ%*$w?umPr<2k0i7NXOrv7#k6GJXco8CEs38xL=pVF^Zri9;Fh{X*O3L53 z?{c8Sk}J8S@fq-sjznDxOkAH*iFsGl4oARjEgax45|&uw!+*5iV#YGc{DY|u2oOh2 zeH^kNJZD3$-uMoWIM@rbft&_G8`=66I>u1Pv~lj>wnuNdna6+MfDXXlvxm~>;`5=S z3A^Mbw~!%peYS2aeM__ycb@zlS1%91uMx#?_^iyyLREHo>XZqbkh7DMGczb#gHO{7JYiGd^~caune*EM(@t)!B1k*UXgG@JIpS; zlVZUWrfB2sfR!Bj7zOTL-~q$wZMg{N@h^Dv!ovN@KEgMcPh^lH2JTl0lc8<3lZR&jPg|m2VwRIiCL|%<;tQeN(imIsb&N*9uOw?$1T`LTbNi}y zrXxDE96yBc;;tgp#T#VE>=auyq zS-TPvwq0IxooWSj-pc-cWgl!(J?mKcS%sPax*zluXE4dr`fh7!E74~OUmz&usLb?bXuDIhte_*lsn zx|@7&j25(rXy~=(7}zLU0ed@JDum(Q&8_`tJ4~~687Ho>&xoA@)8bkCw!L+hi>8av z4AxQ}Adg%=siM!S5UI3d$OeC)15>@P77=;1_X#lr-n3v+ayKlSFvt9e!|5AHMa^IyEyQ&bn><41KBkOj4!94sp7 z5xCQsn9)iuIO6TBU-E-)T@j3fP6Wf?IzGRHi*Fb zOuclU@rGw=S`cBTZ4>>`Av{0bgF5+i_E628q)j*1Kz zRk1mD@Z9YNZ$R1>>+#Jy)9gx_+o35+$IxZ5kx_c1meK`h;RW1-{DB|2*DQT2wQlj9 zQiy{{dcIU^?I^QQ&Wc>fahubICK&zGG&mYGBoqo>a4mSR^Ia8jhNq|^j#n`7y9@XJ zp$nq@H+)f)5|O?YBNWAs74f=Pj^pXnAOW^MLaV$gUtIf8MZjAaRvSWb_NqJ283 zXUIu0*^W`n&C|PfIGrm*01Q8fgJ+sVvbq(G1`T}bVeAh9c9P*dB2 z3SNANfKGWuPrEdzaByY7ur|5MQ+QlP%%RO7?SfA6xDo|qIFMC^$0D#-Uj+hTIGnK} zU(5tO6D8o!l03!L)>HEsz)#T z@&@-%*^h(YE<7SL$}mA28CE-KRw*zJZQ+QvrC-Z~7e>ON;Y}kp+U(^w=!#?RZiA*b z3R@uu!XJ)Jkr~N8GLw_jp?H2esEn@|j0>n4$OSs}!BaYQU^#B?FYC2GO=7&M*gnqu z4pv7rY+2uBhTaaUfICe;_;p@Gf{}pGEJb{e7eO5Vt1n2GfsB~vleQCLfJz{c$&3T- zk_M8xy}W99&=^tSO@lvVX#t%?w>Cg?9ZfYi6VB68&~vS-t@#4j+iH0s-Nq%_3XUvC z&F!Lx#JfBQsPyRn(9DqWJO*p_ssJW`kOGi4XvvYpD3DCH_~MTSSZ|HFRxbOsTYzS7 z9n-((dNRn4}3ljnrbcplJfx*BBX8u^zO8=>2@o; zIDt=MHe+78pj@CCHV2o(eijRNGL8m#MvxQnQ3c-3aUQ>sfpag7=6;YjLtfV(84y2) zXq(j|bXPHX?w`q!{WN(@_H;Wr-ZeXM>&6JJ`dUuc=R{UdHT^`y45k~kkf!Tkd6vQi zrewkKRzCKi#AP3d2}3_7sYqt|PYeTS!^OSsLSX(gt}bkD{UG6jxkCiELUjhG$Zs)@ z41`>fTll|DF4j0^y|X!U-DokkAXj`4_0<7u^?QW+Qzr6vFMlvv4Mh(KBNxZj3l3sp z$TgvEf18g;m)}o^p$y=p2Ff@b%De~j$Y`RgV_?99nDm!I2U7+x^&7tRm7}ZXH}_L3 zPtWXhabA(4g%B#2W?Q|u`K|x#cwH9Ud+oeGYk&Q05(*%~yKZ>xubs6g0GW&`Cbzfr zAV2mueo0mvuaujbGGMjIH&E2>`z2>K7&ZUN@B-$izneoab%7gT&?cMt3WGq7h)A9n zDAneip`xBa%*bUXRU%<)#75~@}+R%q@=b03xTgeulNXd(fJF3H23p{IV;JkP8E;27k_ zTP)xvbZz+f?Wx`6t_<7%4rRy1V)K2zOBMs1^kTXKzOrgVxuf9RD)bw!cM^WknsF{M zaVSy)J*okYV$-BefjGZ6e7qt)<5d6dvSaG;WJQFy*plv!zgt7(Q+z}(@c1Y!fz~n;KNd(9RNOi`I9r3qtFReZ_iyWQCcj|%TRpS@)w-E2o z`3Mq|wuOUm&rK3t5gQ&)%5p~(D2X=rTL3;riB2I1qU>MaDoSYWEJSe?f*C)f;JaYI zF0sR9Hk^N(plu;Ow4UbD05}=@IXPY|eTfOWTVu|hyChr(1)olrj4qOamvbcVO3HUO z!Gd^+#lKRxtI^$(m&sf8m3k9^Rl8rIzY>k(L&?p{R?~T@oD+8J%F+ZVA~1o6DKgw= zxwWmDCs(3-__W9qeL26SqL^=iUYEU=fwH9R%Op_*VhdWASv(f-WmIq9YqfQ4VFb>I zUy~M-Cv3b5NL-rRQcM<-6mlIhuz=?JsHpt(cRsHN{h-X6+@5ANjmF&=b-Y zBgVWuD1ZIQ%3hB{5sFc-=sPdom@sQFaj-f=e|ukNOmb`{0osQ@(w@1(Mos(BRu3{{ z47Or9SY!yslPXkSOY1=ua{019MJ61u3c}?^5hTeR*gDHn5crZCuEO1If_Gej7Z3kQ z92i(!v9J7<`p)zIK!62XR?Z+e?bJ}WY-F+BCyo0tS-JW36J{>k+7VS~BYvmSJR*e% zj*vUr-RVY5R$tBti|9(ays9m%|CuLvz&nzi|ESY2yTBP<987F3q8-Z}v_ypk{aU;> z5xeO`5^7mgZ(&sGjvE`kGuSAR_EKOuQmMOE$@Qsr*B}GGEMOglT2#PX^8TK6$()1Y zoS$$ry0J^e8h4`c$*9LYq^d5wE9{&{hSz=@IFvP&igagIu@0q|SvJbWu3Hg;2k;q^>dExxt}cIIo5Q zzcUV@ds@2Q3jUsFly+%i3{H0;(&{4CkJBPanP<@=>15A5dg^og+s<2iq0%Fm7EVqy z&Vn$a+X6X;VFs)EXU$@7GxG=xBz!V6@27&G)YqwVysk`)L-c4&CQ+OHNTc>CjJl3+ zX8Om|j!K7D|2W+lyfEP&`KcuReJs;~g|jlbu%^zl_;P;-=Tdz1Tc{!m>O&25vuAbg z0mPy^vodFpF1CWQoavz&+$}EUfAD+G;kMOHVP9`G1*9DR6R3Y?dx|n0_;cB<4wdDZ z5Kg-@&f?=>zi1*aYmQ^5WzuY7yZ?@h(TzVVPyd3OfTO84R!GDI3f{_Ua9VcfF01s+ z!Zcu*`Xo0k-W0f}^s#9;>;iW{^Fi%Edq6Wve2^OH?WN4qQBAPu>k${Qm)up1Wb|xA zmvG8+T?x%x;yhJF;=@H6MnB)?FLGN2C#kXjdJC7AlyBY`YcS=gqL3XBY+KS;UM(Oe zGsl%urSp^{a`m*J2Hl9Tp~6ybGot>Wmfc2tz|!Y=`&*R4y^T`!N_bj9c`AmOuB&Mp>gMI z)zTw!YqDN5sWSWz1H08y8aI>IiUOn0t%)KuF&{yU$w!<2IOVZe0GUi!}54ZRts+?uwb)9MBR5?F7Ch%`P-enbHAO%jNw=TisB(i|kr$-QBRT7R(;=plg3x!}h#Rp{d9I(YR2vf0C~;Yl z6_<5FZblF|CLy^KR^^>**4x$Sj?KOFHoJlzbiSZGplI5=Gj>Mm{bh>05Wc6kpgJH1@P|`1f_)X$kr%ZkZ^M*qkTCI8 zzh=dO# z!yqODZ^~>?+88b>LyA%bA`~HWedneY-t319WDZ)gCDk8nS3F!_=+Zx@leI@|!TlMc~Mk%aDDT?L-%_y&cA zLD_}?t&+gkwjXLbyr=y~TXC$O8Ohds)9T>zOM6Q^RVyiDhLha#Q|#I+I_~rtGcbrv&3(ybFIaHZD8JwM33z>kD6_j|O!&K`V4lxNvT}Sq zmTVWjcD(zV>hh;8gC^piD5}dB6Q$rxv(@D3jJ)^|Wj6{|FS8L%1>-6w(~m{&5*#o? zb6XNV2d;h&pW6czLc5RMe1kh=5viJ4+PyiSmC8(W7~731Q4m!tD+kE0*0ce;8pkV7 zEqo(KA_;Y=ty$5EY5@jGWt|STtMUyM7%SoMH zxN_npejqM>md@Z^SAY?UWZ@DbY6<^Eta!^umZq*5Mc>a*k7ZCbde>M!tK3h8Nn}`b z>k&E1y8^b0%2B?y%Y)>(RNsA(#=C7X znTn-Si}66Nh?&mnBkXrH>cfYcZlgMIO$cD+vRdxo(2`Gy87FP7OpOV}~B1 zkqa4^_bD17mqAYT_!$!RHB=}Oy1ziBG^=;!r1`Rm;ny+pW-GuOs$1|#%ArIMW2>_b zF(N<-t+1I?J|)e4e8e5O*dPd!!5yaAxBEf~%B261n3h`*Bf37Ip1P$o`8fGxA@tLI z8rYtd2ovKdW?gvZE$^mRguA5eJ8Rtcgb`=q!;U zFC+w^$aQeysE`KZ-8ZY)eIfClqX~Cje}eMMfJ;jQ+nx4|yB%)B+aL9fh#PZs3ULr~ zHxaTiNs|iWIKSETUr;kbN4_ zz2`WWs6^jE06OeN4r`^WOpMZLiU2Iu=TDCw_k865`EyJy07BTX@S&Z%z>Py>|D&B^@=7+vs|yBtZo98j_(&jfw@(C%o*eA3M}dP|fe_0`Lm5mU@&8Iw*ZlIs@F*x1+! zkqQXK$g2<0V3GI3u~Ix}ivTzhfJsb`mIQ#uUPy9x-5@xl{BaOmOS5$w#~5Vn0U5u{ z(sCQ;uODBzt<*j|`X1iAJd3C9^R56haia2804s#&WLUWYdasUpVw3nt+3q zhG&gz--?8|NK!+Kfe3)>rAaM{+MhKCP1I3NR5mAg+ z4)Am((j|6v`|;;e#0}h?v##!QJw24swJK78NO7WUIx6{ozw}e@bYs(9t2UlJik4T?eK*CABCf;<~iJ3pQ?<$m3`w8{i5;RV9iec&6i1h5;>ADWfdwKrT{ec?A*pgvL2ubSOW85HDWYvn z)@0rxoC72TD=Z)4fjexxX&zEKzn=)}_=rxgyL5cBwkkmE`p1&U@9(Us=FJdHGy(hm zP`_X_!e&R$A=`n9b4P42RKxJR4Vm=eN0(j|4!unlJ{TP&gij6pMnjbJr2}MLe78B6GaL2$pPz@&B>7)wjI!EI6YV0!B)Z+%tVA-Up`Pck_n>NxFIEg1p1r{ z+z2k=EuW8mhuy^I8gb%VCBaaf+mBiG#td?@;rON>3V>B0s;qnZdx6F$O_q!dewkEJ zD{j0egK#^rc8Og!@&+FwpB4oBJ`rN*VrDu>gZZ=~*xl9^pEKLkLEp^VHHNZWe3E=& zw(ux{pazj@k(lr3Q-f|gw8lnpRUgm%R8Ppb`6;A;v9yP<+i~X+i8Y*fK#epUlKUp= zX!ScKGOKqaHt_eh5aU|%5upjH$Yi94ereSCv*a~_fSN6`fuEY;3jKk;&d|bHWJnlZ z6!4wK8NTY7+RF|m^}YP2xTYV`f; zW_hMhq`%mjJlQLKOA5j|db-O%mdIW$UryEujLqoaw;+{N2?6H1hHPSvH@?2D-B?_A z9p5%`V*G!@+`|xk_knSBq`1+By#KjL7}abRjrDt~L@Q|R2~t{I#tTaaUu=QXbi+mT z2plE=H_%4}axLsqGrhL|(lp%%R{D@~6M6J849=q*zV& z`_|Rs#hO~ixJ~Hg->#qJ=55^%A1cZ>X%j$Im?*+0LlUJNppP)Ixf)u@K@DwY)*urD zLb3Oc6`vo}-3ra#)x(Q?LvM76)oc`qES8KM8D$-s#s6urEV1IpgxK>REr;;l0}_YAd~OfPavzW)1x0d^3(V z_*m>a0c6o0*VGUz(iM?O4mdtMf|9);xEW6#aaZSMAg7xY?{!9%Z8TEe8C zqMMZy1AKnz+NY0f`=@>H)3)>Tg<* z!DB9(3sEg8=1c4M>NmOZSH<}>H%a@S%_P08j^9xAs!*io5i`&BAzS!)TD00YdCrYs za&X9Ua0phc1(A$B)O{vJ8zW{_hJJEt@8HXo(>~n=kPu|0K@a*G4M>X05JC;`wn=Ov zEqM-U_N5L>sjr0A%>Rkb9V8jN&LzN z2DQ>rCJaVi$EG2ZzhMaO95YScvr^%A?ZFy8UJD0hn=Z2&0%y0WtbV>bwT(j@^rQ8| z6PC1;>@2*PXm4=V!?YjpyHa10ab>>7BB=h-wz2@vnqP@mgtc$;vGNCem^9J|O06S6BA z?x)ZYw8s>D{I5amZ2zw$G#by&ri=Qs2mf!%Rv(g$Gc)lfo_L;Dh_SM#%eK_*dg}FN zgbVer^Wqj$H`KX1wff|IX1=yGD6(4JhTLX?9$+a|4`l))Yhp~%FwSZA=Ux^JQ0g+{ ze&`$0%+FVM$?adCSXWiTdKd9KcM`u!RDG+FI}$ZlqhhMB>|mmBrZ7qA?2|K|=RMZ3 zI|Jw08@4#FiPguIhCRQ%%$D=)76_}>el`p4tNND`2H3|2jjzI8_iJksYKd~2%v^uS z5+5OGG|gHs$=Eg%6+?mabP?1Y!4}U46z_@Q3N%d+@IW{s(_PWRCG)QVy%ORYnDXsX z)X+Rlpu8lk88exY8tmWFuMW&89{Q8_e!cdB@{yfzy|rYjjm4(e3#MF{#9fP zMjfu=Po&J@ekv88gKpQ7zftF-b8`8L+=_;9NT5+WDzeEGpWBgoWG^aiX_6rkxgI*M z(Sewov&4ob0VwE~12WVg7m;fgv_R|=0ofUtN0dLX_R_C?UaZsfUlS3hbUo;yUt^wwQGA$_52;3p-D{k%B zBPVrPad!8Isa(_>4CfZ4~x zGR6`VhsnE+a+c`EmrI8*KgF!xD`8Ws$s0_9T#ueA1$0&uw{W3BVnq#Vbd9>t_+(iG z331c8?}ltAa^cF5qR2D?KHCgQ$zs3wvXi}!RS(q6l0H*!mEgD@pe1cYS}=NR1MWqfNsvS~ZS%YEbzEtns|-T~$hlf~akHV1#sM6K{~M}d=W zjhgOz3GRnLVUmH6B{q;)`mX~HZU?Df2_XaPN_~0po0gG2*yE;mh?lw{9J=nbT)l|e zVpga#`|fkFzEnQo%pyeTR!rUxnG#{j__(nM(34Zmb_6I~Z2p0(RJNPj-c6@+6+9@H zFNZWIIaysksHpUPxQ;x8?UDiNMJPbZJ2Q7By*R!+o>BADNX@;goV-oILcV{j#USfw zj1T(TGYZymp)!d#7~C!NI733g_Vg6;IKKS$sHeDDAZD%3KF`EW+|qh*&pw)o-sgNl8-V=z_9vuVUQGn4LSPY>bQT?&H? zBnEE@mHrfm79W>qb%uDId3d~ED6<%pYT?fq-b|tS-9(ryeLO04{$1sv5G>(742?60zgx0q$s?Nb;&P6sx&J}xb>SFZ5aqh{8 z4OW8Q#;F|B0;M^PC8g|w^Q~C9j)Zqyc%7N`PU6$dXN!MV(D;}N4gRjKdi zvxb?6%pT5v#Eh5YXfB`;ol-DPl#r7=uO=N2e(<|(|Ml{YM9bD+uexx038|XqyqLy$ zT~A%)VQ^j%^@rbCe2axqh+(Uafiv`Y>RQ&OtZt} zxhv}o$JBCJw`63P!j!UXNcREOyHme*!TwITf9BRe7AA=8h4*W}o#p4bARG`%O@ODY zq*g-#Us8cThgZ+6S^<4&Xc;P!LY2Cc7x$?_ya9zOzQ~PL0^2>aL0^ z2nkESG(H2wU{aGE$dGU5NjVxiotsjs2S}K0g};%c_N(QDj~9^t+3C82n_c_;=N60W zx6W$-q6z;xnh`Ow6=e&2`Nw;gRHk>dzMGzxk?$6wD zg}kXLiyxW{7_JSrFqZWP1Lqdk@l{32I5SJ763!cAbjUR!t<%|rVEW^o?pz`l7C*RG zKgsH%2VY6GcCMXwb&TuLe$ApqMXhPz97^T2R)1XL@#dfs9^5ajorCWdi@dJa?Q{+m++6i+zX=C~n7-}d z`^dztd&6_h^>&KO+VL7Ni4iv0*&iL%I=)9$MR@;>KW~nt7cO(JK>18~)G?Jc7Gd(e zy*7a(3JqN)sYJK};>g`HNiVG&F)vWs^YER0>nEm+RDn3Kd^D%1;TuUXQ{aDO!i)=}uMEPI!LMDkTsf zzCFu(-ROx9n8<$C*>x2kx1LR|ZO4gkMb%c7ilX4+1xyzAY`vGt9MFKkmQFXO3`O}P zN^ZalSv+;Bb}X!hYoft@5!sOKJ>qmasb!K`+~z&7fPzb2er0%8DiQW0RTQw0 zgS!{K(cwjT17ouPH+%*w8Am8!c4S3bY-FDW3 zO7T(TOdGI>HDk?lP`xwAEKhu4J`yYQUuB$A}6nzy$z-Jy9LZ08mtzKuK`zZ8#TEP}2$AcjO)FDncSl%Dk8lmI42R1(|zMZ_FQjiCNQY1p;& z$kJM2-amGl@QbnlI!qd99%V@r2ZQa-Z@TzhMsOGA&E-KAQ~xhiFrek-Fbf^Z(PwNr z4PU7&%m#^#fu({&-~qCVZ4Xa>>R_?&KsI`J2)Q}oPo*Ah|12^d0H6rAa|@5#tJ@Jl ziby=Mn;)gYZZJJFj@a@>0vfuO_l$hE0pMHX{;Wr?P6jLhbG?_H4x{4(a(}0T7lLShKYw63^5A@Ks?0r8gaYz0)Wmr z%GtuQvSl=y6(Yp{qz;tVKJm1^&+zkQoGwgR$WNj#-U;5*Z%?;GY}H&#fu~&@LF@@H+~*;}WIi z(N>ZT%Y|p%EuBlpDrWp?62qiG=4ddF`w{nTxbF6K3EP9n4z)=<@+d$uQ|KmU5z zhwnKNTe+wAL(wM)fPv_8P&2*g!@5(^8c5g^3joy^0A!K0`M^<7FdGa^m-B0whoF;x zT|OyjJVmFS#3@DXia^pQEoCw!1-@?T*l=G5QN*wKZC4obV#-8@FGYMEv_k}9t*GQ$ zUE0%;oey|x-y#5{IE;$~-j0cP1tCV1?tto_YN!W+_-vNZ1`6SmH~5jqsK4(ICcwvA zaJC6^|7&$84aw4mk^QB;>{df!##9$`#DFLkkB$MTxxQg$LpuIlRMiF^JjA@P6S;=y zLMKCsKwo@@ypMjgFR9z3NPQbM*CnC|H*GT`;lITo!KNm7M@ zMKJpO8HWr;gWFuCcFR}pIN;Hj?!e3KqBX~*z$otyJ7+nnp;hC3FNc3OA_APC*PE{4 za4}tkC0by8`0vjEV0m7)D@D`*PRh&KOJOf9q%lH2YgQ-iDZihlLeJm3ggl(~@b301 zWH~rJ%?3|mRq;oYtN<}Z{>X!#*sgW7oX~FTMiahcM8aJ~hg%@G2xSU8=lkDyi3jJl zfRjTwKxo*Cy4dc?8h$lYs(!NIoAXC%ET6`o5rU7A>OFD0Ip>#t1ZlK%P+m3M7yg?; zlW^lSZ=#yejpPmHyQn1oo#=zU0gC-#=PlMlW6?i@@&lACpDnU z;pvV4(2Nn0{#adAA*rICNdKKmxF1UA3xO_vL=jS()d^1+!1VKj%CZ~)1p$;MkoZo_ zJ&Je2*~c!4w6L|q$9|uQ;)!dRFr@>)z$@>_wHeSJR(Q_rEsclz_x%GB6FMls(J(JR z9T&>#sRq=T`|Hc{HR<{aD|7TF>d#d-=v%3=&Z%4bWm1 zSyJ>O{6U?Zv2yZ0y;4qhy}C+Re%emg$ies5)U`~L(A1@-!QXJ_6x**T%nXQkPWk57W#;SP;FseqvQ>)F+5 zT@L!!6>qnF>WAlh$yKj1Uf(CTny-^X8l}p{dOt~y-|?Fy(7lOUgU0Ub#w_$wr#l}dOT?308Ok}$mXy=Kf3c38sr zcG%r*^FaBy<+_(~%(DRzY3=&FEa&Dh`Hres?e|Fkkp5Sx4oT&gR8Vlk=KcLx71V8% zFG)dbU2;}ZOs&wTx_14sen~e4z4NcXa8hxR?g$=XHL87*P^ZS-+iZ8Y_Tw7W@nc>( zb=gTer!M=ys89Obp#Qr%GU^EL36*?J3T5S0%)Q&-YPzrD&#H}eQoD1?tC_3R(a(HY z91IkHlJK9)#SY;4=*ZGG?f@3g0r2eBH*}3UYMBprf20F`oU;7<)9>lMIzS%+F0X3& zO3f<0_1+KPu8j=vd=-CUs?%XWDd~P-jjTj$#0k{OUx(-5TvL3c(y}9Y#D(!+kM#eo z<*^M%7cJFurGVbk2*lSjd5*KnkF_gW8%tP?MtK5C5~#6po&+x=f!7L>si;9MFeM=} zX$M@aV`c*#y`GqwY4wR?>DPv&AUdzlBjLw~QIxLId4So=iJJcP6(C8w+^c#!XluYs#qe*hF?YtFkB@ zSoqG{N85*;PUpqZS?(+VUFO^GpLG;3jy~|=02a;xn6;zog;)HV^)(^S0{r;f+L8%n zQ3qriC1`~U+Oaen(pZvdxQaf;*`tuh*P#_;l)g~Lyij<{ils8O8m4i{`a$++^gfp0 z*%CH*<}Fk|SqV{?4eS{y*wC3g{c4&@+69d=k5no<6%T4xl(XwHo*Q!VtX~16*f~z5oAi!{e^Rhlf2slK;4+*@;Q$ui0jT?9Y?O8P9>9!g=6g#p;p97j>_ra;aGxB27vlQ=FRr`s zSY{p_fDVnwT*}!EL4=8($fkhmN#mp6#yo9Pzz_cQE-<_?yZSdY9(A|bIirVWdQCD8YTb$0000000000 z0000000000000000001hnF+7}6@Yt7P2S=F#1a7DzH$-ZCI&$P&wD2#bAVN2Kn2&C5rSM5_!k%<`iO5(F`Xl z000000000000000000000000000000000000000000000000000AOzT3wA8Ws3myl QQ2+n{07*qoM6N<$f@5^xI{*Lx diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md index 29164da307b..432caa8476f 100644 --- a/doc/user/project/clusters/serverless/index.md +++ b/doc/user/project/clusters/serverless/index.md @@ -2,818 +2,10 @@ stage: Configure group: Configure info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +remove_date: '2022-08-22' +redirect_to: '../../../../update/removals.md#gitlab-serverless' --- -# Serverless (DEPRECATED) **(FREE)** +# Serverless (removed) **(FREE)** -> - Introduced in GitLab 11.5. -> - [Deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/6) in GitLab 14.3. - -WARNING: -Serverless is currently in [alpha](../../../../policy/alpha-beta-support.md#alpha-features). - -## Overview - -Serverless architectures offer Operators and Developers the ability write highly scalable applications without provisioning a single server. - -GitLab supports several ways deploy Serverless applications in both Kubernetes Environments and also major cloud Function as a Service (FaaS) environments. - -Currently we support: - -- [Knative](#knative): Build Knative applications with Knative and `gitlabktl` on GKE and EKS. -- [AWS Lambda](aws.md): Create serverless applications via the Serverless Framework and GitLab CI/CD. - -## Knative - -Run serverless workloads on Kubernetes using [Knative](https://cloud.google.com/knative/). - -Knative extends Kubernetes to provide a set of middleware components that are useful to build -modern, source-centric, container-based applications. Knative brings some significant benefits out -of the box through its main components: - -- [Serving](https://github.com/knative/serving): Request-driven compute that can scale to zero. -- [Eventing](https://github.com/knative/eventing): Management and delivery of events. - -For more information on Knative, visit the [Knative docs repository](https://github.com/knative/docs). - -With GitLab Serverless, you can deploy both FaaS and serverless applications. - -## Prerequisites - -To run Knative on GitLab, you need: - -1. **Existing GitLab project:** You need a GitLab project to associate all resources. The simplest way to get started: - - If you are planning on [deploying functions](#deploying-functions), - clone the [functions example project](https://gitlab.com/knative-examples/functions) to get - started. - - If you are planning on [deploying a serverless application](#deploying-serverless-applications), - clone the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get - started. -1. **Kubernetes Cluster:** An RBAC-enabled Kubernetes cluster is required to deploy Knative. - The simplest way to get started is to add a cluster using the GitLab [GKE integration](../add_remove_clusters.md). - The set of minimum recommended cluster specifications to run Knative is 3 nodes, 6 vCPUs, and 22.50 GB memory. -1. **GitLab Runner:** A runner is required to run the CI jobs that deploy serverless - applications or functions onto your cluster. You can install GitLab Runner - onto the [existing Kubernetes cluster](https://docs.gitlab.com/runner/install/kubernetes.html). -1. **Domain Name:** Knative provides its own load balancer using Istio, and an - external IP address or hostname for all the applications served by Knative. Enter a - wildcard domain to serve your applications. Configure your DNS server to use the - external IP address or hostname for that domain. -1. **`.gitlab-ci.yml`:** GitLab uses [Kaniko](https://github.com/GoogleContainerTools/kaniko) - to build the application. We also use [GitLab Knative tool](https://gitlab.com/gitlab-org/gitlabktl) - CLI to simplify the deployment of services and functions to Knative. -1. **`serverless.yml`** (for [functions only](#deploying-functions)): When using serverless to deploy functions, the `serverless.yml` file - contains the information for all the functions being hosted in the repository as well as a reference - to the runtime being used. -1. **`Dockerfile`** (for [applications only](#deploying-serverless-applications)): Knative requires a - `Dockerfile` in order to build your applications. It should be included at the root of your - project's repository and expose port `8080`. `Dockerfile` is not require if you plan to build serverless functions - using our [runtimes](https://gitlab.com/gitlab-org/serverless/runtimes). -1. **Prometheus** (optional): The [Prometheus cluster integration](../../../clusters/integrations.md#prometheus-cluster-integration) - allows you to monitor the scale and traffic of your serverless function/application. -1. **Logging** (optional): Configuring logging allows you to view and search request logs for your serverless function/application. - See [Configuring logging](#configuring-logging) for more information. - -## Configuring Knative - -> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/58941) in GitLab 12.0. - -1. Follow the steps to - [add a Kubernetes - cluster](../add_remove_clusters.md). - -1. Ensure GitLab can manage Knative: - - For a non-GitLab managed cluster, ensure that the service account for the token - provided can manage resources in the `serving.knative.dev` API group. - - For a GitLab managed cluster, if you added the cluster in [GitLab 12.1 or later](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/30235), - then GitLab already has the required access and you can proceed to the next step. - - Otherwise, you need to manually grant the GitLab service account the ability to manage - resources in the `serving.knative.dev` API group. Since every GitLab service account - has the `edit` cluster role, the simplest way to do this is with an - [aggregated ClusterRole](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#aggregated-clusterroles) - adding rules to the default `edit` cluster role: First, save the following YAML as - `knative-serving-only-role.yaml`: - - ```yaml - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRole - metadata: - name: knative-serving-only-role - labels: - rbac.authorization.k8s.io/aggregate-to-edit: "true" - rules: - - apiGroups: - - serving.knative.dev - resources: - - configurations - - configurationgenerations - - routes - - revisions - - revisionuids - - autoscalers - - services - verbs: - - get - - list - - create - - update - - delete - - patch - - watch - ``` - - Then run the following command: - - ```shell - kubectl apply -f knative-serving-only-role.yaml - ``` - - Alternatively, permissions can be granted on a per-service account basis - using `Role`s and `RoleBinding`s (see the [Kubernetes RBAC - documentation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) - for more information). - -1. Follow the steps to deploy [functions](#deploying-functions) - or [serverless applications](#deploying-serverless-applications) onto your - cluster. - -1. **Optional:** For invocation metrics to show in GitLab, additional Istio metrics need to be configured in your cluster. For example, with Knative v0.9.0, you can use [this manifest](https://gitlab.com/gitlab-org/charts/knative/-/raw/v0.10.0/vendor/istio-metrics.yml). - -## Supported runtimes - -Serverless functions for GitLab can be run using: - -- [GitLab-managed](#gitlab-managed-runtimes) runtimes. -- [OpenFaaS](#openfaas-runtimes) runtimes. - -If a runtime is not available for the required programming language, consider deploying a -[serverless application](#deploying-serverless-applications). - -### GitLab-managed runtimes - -The following GitLab-managed [runtimes](https://gitlab.com/gitlab-org/serverless/runtimes) -are available: - -- `go` (proof of concept) -- `nodejs` -- `ruby` - -You must provide a `Dockerfile` to run serverless functions if no runtime is specified. - -### OpenFaaS runtimes - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/29253) in GitLab 12.5. - -[OpenFaaS classic runtimes](https://github.com/openfaas/templates#templates-in-store) can be used with GitLab serverless. - -OpenFaas runtimes are available for the following languages: - -- C# -- Go -- NodeJS -- PHP -- Python -- Ruby - -Runtimes are specified using the pattern: `openfaas/classic/`. The following -example shows how to define a function in `serverless.yml` using an OpenFaaS runtime: - -```yaml -hello: - source: ./hello - runtime: openfaas/classic/ruby - description: "Ruby function using OpenFaaS classic runtime" -``` - -`handler` is not needed for OpenFaaS functions. The location of the handler is defined -by the conventions of the runtime. - -See the [`ruby-openfaas-function`](https://gitlab.com/knative-examples/ruby-openfaas-function) -project for an example of a function using an OpenFaaS runtime. - -## Deploying functions - -> Introduced in GitLab 11.6. - -You can find and import all the files referenced in this doc in the -**[functions example project](https://gitlab.com/knative-examples/functions)**. - -Follow these steps to deploy a function using the Node.js runtime to your -Knative instance (you can skip these steps if you've cloned the example -project): - -1. Create a directory to house the function. In this example we will - create a directory called `echo` at the root of the project. - -1. Create the file to contain the function code. In this example, our file is called `echo.js` and is located inside the `echo` directory. If your project is: - - Public, continue to the next step. - - Private, you must [create a GitLab deploy token](../../deploy_tokens/index.md#creating-a-deploy-token) with `gitlab-deploy-token` as the name and the `read_registry` scope. - -1. `.gitlab-ci.yml`: this defines a pipeline used to deploy your functions. - It must be included at the root of your repository: - - ```yaml - include: - - template: Serverless.gitlab-ci.yml - - functions:build: - extends: .serverless:build:functions - environment: production - - functions:deploy: - extends: .serverless:deploy:functions - environment: production - ``` - - This `.gitlab-ci.yml` creates jobs that invoke some predefined commands to - build and deploy your functions to your cluster. - - `Serverless.gitlab-ci.yml` is a template that allows customization. - You can either import it with `include` parameter and use `extends` to - customize your jobs, or you can inline the entire template by choosing it - from **Apply a template** dropdown when editing the `.gitlab-ci.yml` file through - the user interface. - -1. `serverless.yml`: this file contains the metadata for your functions, - such as name, runtime, and environment. - - It must be included at the root of your repository. - The following is a sample `echo` function which shows the required structure - for the file. - - You can find the relevant files for this project in the [functions example project](https://gitlab.com/knative-examples/functions). - - ```yaml - service: functions - description: "GitLab Serverless functions using Knative" - - provider: - name: triggermesh - envs: - FOO: value - secrets: - - my-secrets - - functions: - echo-js: - handler: echo-js - source: ./echo-js - runtime: gitlab/runtimes/nodejs - description: "node.js runtime function" - envs: - MY_FUNCTION: echo-js - secrets: - - my-secrets - ``` - -Explanation of the fields used above: - -### `service` - -| Parameter | Description | -|-----------|-------------| -| `service` | Name for the Knative service which serves the function. | -| `description` | A short description of the `service`. | - -### `provider` - -| Parameter | Description | -|-----------|-------------| -| `name` | Indicates which provider is used to execute the `serverless.yml` file. In this case, the TriggerMesh middleware. | -| `envs` | Includes the environment variables to be passed as part of function execution for **all** functions in the file, where `FOO` is the variable name and `BAR` are the variable contents. You may replace this with your own variables. | -| `secrets` | Includes the contents of the Kubernetes secret as environment variables accessible to be passed as part of function execution for **all** functions in the file. The secrets are expected in `INI` format. | - -### `functions` - -In the `serverless.yml` example above, the function name is `echo` and the -subsequent lines contain the function attributes. - -| Parameter | Description | -|-----------|-------------| -| `handler` | The function's name. | -| `source` | Directory with sources of a functions. | -| `runtime` (optional)| The runtime to be used to execute the function. This can be a runtime alias (see [Runtime aliases](#runtime-aliases)), or it can be a full URL to a custom runtime repository. When the runtime is not specified, we assume that `Dockerfile` is present in the function directory specified by `source`. | -| `description` | A short description of the function. | -| `envs` | Sets an environment variable for the specific function only. | -| `secrets` | Includes the contents of the Kubernetes secret as environment variables accessible to be passed as part of function execution for the specific function only. The secrets are expected in `INI` format. | - -### Deployment - -#### Runtime aliases - -The optional `runtime` parameter can refer to one of the following runtime aliases (also see [Supported runtimes](#supported-runtimes)): - -| Runtime alias | Maintained by | -|-------------|---------------| -| `gitlab/runtimes/go` | GitLab | -| `gitlab/runtimes/nodejs` | GitLab | -| `gitlab/runtimes/ruby` | GitLab | -| `openfaas/classic/csharp` | OpenFaaS | -| `openfaas/classic/go` | OpenFaaS | -| `openfaas/classic/node` | OpenFaaS | -| `openfaas/classic/php7` | OpenFaaS | -| `openfaas/classic/python` | OpenFaaS | -| `openfaas/classic/python3` | OpenFaaS | -| `openfaas/classic/ruby` | OpenFaaS | - -After the `.gitlab-ci.yml` template has been added and the `serverless.yml` file -has been created, pushing a commit to your project results in a CI pipeline -being executed which deploys each function as a Knative service. After the -deploy stage has finished, additional details for the function display -under **Infrastructure > Serverless platform**. - -![serverless page](img/serverless-page_v14_0.png) - -This page contains all functions available for the project, the description for -accessing the function, and, if available, the function's runtime information. -The details are derived from the Knative installation inside each of the project's -Kubernetes cluster. Click on each function to obtain detailed scale and invocation data. - -The function details can be retrieved directly from Knative on the cluster: - -```shell -kubectl -n "$KUBE_NAMESPACE" get services.serving.knative.dev -``` - -The sample function can now be triggered from any HTTP client using a simple `POST` call: - - 1. Using curl (replace the URL on the last line with the URL of your application): - - ```shell - curl \ - --header "Content-Type: application/json" \ - --request POST \ - --data '{"GitLab":"FaaS"}' \ - "http://functions-echo.functions-1.functions.example.com/" - ``` - - 1. Using a web-based tool (such as Postman or Restlet) - - ![function execution](img/function-execution.png) - -### Secrets - -To access your Kubernetes secrets from within your function, the secrets should be created under the namespace of your serverless deployment and specified in your `serverless.yml` file as above. -You can create secrets in several ways. The following sections show some examples. - -#### CLI example - -```shell -kubectl create secret generic my-secrets -n "$KUBE_NAMESPACE" --from-literal MY_SECRET=imverysecure -``` - -#### Part of deployment job - -You can extend your `.gitlab-ci.yml` to create the secrets during deployment using the [CI/CD variables](../../../../ci/variables/index.md) -stored securely under your GitLab project. - -```yaml -deploy:function: - stage: deploy - environment: production - extends: .serverless:deploy:functions - before_script: - - kubectl create secret generic my-secret - --from-literal MY_SECRET="$GITLAB_SECRET_VARIABLE" - --namespace "$KUBE_NAMESPACE" - --dry-run -o yaml | kubectl apply -f - -``` - -### Running functions locally - -Running a function locally is a good way to quickly verify behavior during development. - -Running functions locally requires: - -- Go 1.12 or newer installed. -- Docker Engine installed and running. -- `gitlabktl` installed using the Go package manager: - - ```shell - GO111MODULE=on go get gitlab.com/gitlab-org/gitlabktl - ``` - -To run a function locally: - -1. Navigate to the root of your GitLab serverless project. -1. Build your function into a Docker image: - - ```shell - gitlabktl serverless build - ``` - -1. Run your function in Docker: - - ```shell - docker run -itp 8080:8080 - ``` - -1. Invoke your function: - - ```shell - curl "http://localhost:8080" - ``` - -## Deploying Serverless applications - -> Introduced in GitLab 11.5. - -Serverless applications are an alternative to [serverless functions](#deploying-functions). -They're useful in scenarios where an existing runtime does not meet the needs of -an application, such as one written in a language that has no runtime available. -Note though that serverless applications should be stateless. - -You can reference and import the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) -to get started. Add the following `.gitlab-ci.yml` to the root of your repository -(you may skip this step if you've previously cloned the previously mentioned, -sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app)): - -```yaml -include: - - template: Serverless.gitlab-ci.yml - -build: - extends: .serverless:build:image - -deploy: - extends: .serverless:deploy:image -``` - -`Serverless.gitlab-ci.yml` is a template that allows customization. -You can either import it with `include` parameter and use `extends` to -customize your jobs, or you can inline the entire template by choosing it -from **Apply a template** dropdown when editing the `.gitlab-ci.yml` file through -the user interface. - -A `serverless.yml` file is not required when deploying serverless applications. - -### Deploy the application with Knative - -With all the pieces in place, the next time a CI pipeline runs the Knative application deploys. Navigate to -**CI/CD > Pipelines** and click the most recent pipeline. - -### Function details - -Go to the **Infrastructure > Serverless platform** page to see the final URL of your functions. - -![function_details](img/function-list_v12_7.png) - -### Invocation metrics - -On the same page as above, click on one of the function -rows to bring up the function details page. - -![function_details](img/function-details-loaded_v14_0.png) - -The pod count gives you the number of pods running the serverless function instances on a given cluster. - -For the Knative function invocations to appear, the -[Prometheus cluster integration must be enabled](../../../clusters/integrations.md#prometheus-cluster-integration). - -Once Prometheus is enabled, a message may appear indicating that the metrics data _is -loading or is not available at this time._ It appears upon the first access of the -page, but should go away after a few seconds. If the message does not disappear, then it -is possible that GitLab is unable to connect to the Prometheus instance running on the -cluster. - -## Configuring logging - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33330) in GitLab 12.5. - -### Prerequisites - -- A GitLab-managed cluster. -- `kubectl` installed and working. - -Running `kubectl` commands on your cluster requires setting up access to the -cluster first. For clusters created on: - -- GKE, see [GKE Cluster Access](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl) -- Other platforms, see [Install and Set Up kubectl](https://kubernetes.io/docs/tasks/tools/). - -### Enable request log template - -Run the following command to enable request logs: - -```shell -kubectl edit cm -n knative-serving config-observability -``` - -Copy the `logging.request-log-template` from the `data._example` field to the data field one level up in the hierarchy. - -### Enable request logs - -Run the following commands to install Elasticsearch, Kibana, and Filebeat into a `kube-logging` namespace and configure all nodes to forward logs using Filebeat: - -```shell -kubectl apply -f https://gitlab.com/gitlab-org/serverless/configurations/knative/raw/v0.7.0/kube-logging-filebeat.yaml -kubectl label nodes --all beta.kubernetes.io/filebeat-ready="true" -``` - -### Viewing request logs - -To view request logs: - -1. Run `kubectl proxy`. -1. Navigate to [Kibana UI](http://localhost:8001/api/v1/namespaces/kube-logging/services/kibana/proxy/app/kibana). - -Or: - -1. Open the [Kibana UI](http://localhost:8001/api/v1/namespaces/kube-logging/services/kibana/proxy/app/kibana). -1. Click on **Discover**, then select `filebeat-*` from the dropdown on the left. -1. Enter `kubernetes.container.name:"queue-proxy" AND message:/httpRequest/` into the search box. - -## Enabling TLS for Knative services - -By default, a GitLab serverless deployment is served over `http`. To serve -over `https`, you must manually obtain and install TLS certificates. - -The simplest way to accomplish this is to use Certbot to -[manually obtain Let's Encrypt certificates](https://knative.dev/docs/serving/using-a-tls-cert/#using-certbot-to-manually-obtain-let-s-encrypt-certificates). -Certbot is a free, open source software tool for automatically using Let's Encrypt -certificates on manually-administered websites to enable HTTPS. - -The following instructions relate to installing and running Certbot on a Linux -server that has Python 3 installed, and may not work on other operating systems -or with other versions of Python. - -1. Install Certbot by running the - [`certbot-auto` wrapper script](https://eff-certbot.readthedocs.io/install.html#certbot-auto). - On the command line of your server, run the following commands: - - ```shell - wget https://dl.eff.org/certbot-auto - sudo mv certbot-auto /usr/local/bin/certbot-auto - sudo chown root /usr/local/bin/certbot-auto - sudo chmod 0755 /usr/local/bin/certbot-auto - /usr/local/bin/certbot-auto --help - ``` - - To check the integrity of the `certbot-auto` script, run: - - ```shell - wget -N https://dl.eff.org/certbot-auto.asc - gpg2 --keyserver ipv4.pool.sks-keyservers.net --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2 - gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc /usr/local/bin/certbot-auto - ``` - - The output of the last command should look something like: - - ```shell - gpg: Signature made Mon 10 Jun 2019 06:24:40 PM EDT - gpg: using RSA key A2CFB51FA275A7286234E7B24D17C995CD9775F2 - gpg: key 4D17C995CD9775F2 marked as ultimately trusted - gpg: checking the trustdb - gpg: marginals needed: 3 completes needed: 1 trust model: pgp - gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u - gpg: next trustdb check due at 2027-11-22 - gpg: Good signature from "Let's Encrypt Client Team " [ultimate] - ``` - -1. Run the following command to use Certbot to request a certificate - using DNS challenge during authorization: - - ```shell - /usr/local/bin/certbot-auto certonly --manual --preferred-challenges dns -d '*..example.com' - ``` - - Where `` is the namespace created by GitLab for your serverless project (composed of `--`) and - `example.com` is the domain being used for your project. If you are unsure what the namespace of your project is, navigate - to the **Infrastructure > Serverless platform** page of your project and inspect - the endpoint provided for your function/app. - - ![function_endpoint](img/function-endpoint.png) - - In the above image, the namespace for the project is `node-function-11909507` and the domain is `knative.info`, thus - certificate request line would look like this: - - ```shell - ./certbot-auto certonly --manual --preferred-challenges dns -d '*.node-function-11909507.knative.info' - ``` - - The Certbot tool walks you through the steps of validating that you own each domain that you specify by creating TXT records in those domains. - After this process is complete, the output should look something like this: - - ```shell - IMPORTANT NOTES: - - Congratulations! Your certificate and chain have been saved at: - /etc/letsencrypt/live/namespace.example.com/fullchain.pem - Your key file has been saved at: - /etc/letsencrypt/live/namespace.example/privkey.pem - Your cert will expire on 2019-09-19. To obtain a new or tweaked - version of this certificate in the future, simply run certbot-auto - again. To non-interactively renew *all* of your certificates, run - "certbot-auto renew" - -----BEGIN PRIVATE KEY----- - - Your account credentials have been saved in your Certbot - configuration directory at /etc/letsencrypt. You should make a - secure backup of this folder now. This configuration directory will - also contain certificates and private keys obtained by Certbot so - making regular backups of this folder is ideal. - ``` - -1. Create certificate and private key files. Using the contents of the files - returned by Certbot, create two files in order to create the - Kubernetes secret: - - Run the following command to see the contents of `fullchain.pem`: - - ```shell - sudo cat /etc/letsencrypt/live/node-function-11909507.knative.info/fullchain.pem - ``` - - Output should look like this: - - ```shell - -----BEGIN CERTIFICATE----- - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b4ag== - -----END CERTIFICATE----- - -----BEGIN CERTIFICATE----- - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - K2fcb195768c39e9a94cec2c2e30Qg== - -----END CERTIFICATE----- - ``` - - Create a file with the name `cert.pem` with the contents of the entire output. - - Once `cert.pem` is created, run the following command to see the contents of `privkey.pem`: - - ```shell - sudo cat /etc/letsencrypt/live/namespace.example/privkey.pem - ``` - - Output should look like this: - - ```shell - -----BEGIN PRIVATE KEY----- - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df - -----BEGIN CERTIFICATE----- - fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6 - 4f294d1eaca42b8692017b4262== - -----END PRIVATE KEY----- - ``` - - Create a new file with the name `cert.pk` with the contents of the entire output. - -1. Create a Kubernetes secret to hold your TLS certificate, `cert.pem`, and - the private key `cert.pk`: - - NOTE: - Running `kubectl` commands on your cluster requires setting up access to the cluster first. - For clusters created on GKE, see - [GKE Cluster Access](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl). - For other platforms, [install `kubectl`](https://kubernetes.io/docs/tasks/tools/). - - ```shell - kubectl create --namespace istio-system secret tls istio-ingressgateway-certs \ - --key cert.pk \ - --cert cert.pem - ``` - - Where `cert.pem` and `cert.pk` are your certificate and private key files. Note that the `istio-ingressgateway-certs` secret name is required. - -1. Configure Knative to use the new secret that you created for HTTPS - connections. Run the - following command to open the Knative shared `gateway` in edit mode: - - ```shell - kubectl edit gateway knative-ingress-gateway --namespace knative-serving - ``` - - Update the gateway to include the following `tls:` section and configuration: - - ```shell - tls: - mode: SIMPLE - privateKey: /etc/istio/ingressgateway-certs/tls.key - serverCertificate: /etc/istio/ingressgateway-certs/tls.crt - ``` - - Example: - - ```shell - apiVersion: networking.istio.io/v1alpha3 - kind: Gateway - metadata: - # ... skipped ... - spec: - selector: - istio: ingressgateway - servers: - - hosts: - - "*" - port: - name: http - number: 80 - protocol: HTTP - - hosts: - - "*" - port: - name: https - number: 443 - protocol: HTTPS - tls: - mode: SIMPLE - privateKey: /etc/istio/ingressgateway-certs/tls.key - serverCertificate: /etc/istio/ingressgateway-certs/tls.crt - ``` - - After your changes are running on your Knative cluster, you can begin using the HTTPS protocol for secure access your deployed Knative services. - In the event a mistake is made during this process and you need to update the cert, you must edit the gateway `knative-ingress-gateway` - to switch back to `PASSTHROUGH` mode. Once corrections are made, edit the file again so the gateway uses the new certificates. - -## Using an older version of `gitlabktl` - -There may be situations where you want to run an older version of `gitlabktl`. This -requires setting an older version of the `gitlabktl` image in the `.gitlab-ci.yml` file. - -To set an older version, add `image:` to the `functions:deploy` block. For example: - -```yaml -functions:deploy: - extends: .serverless:deploy:functions - environment: production - image: registry.gitlab.com/gitlab-org/gitlabktl:0.5.0 -``` - -Different versions are available by changing the version tag at the end of the registry URL in the -format `registry.gitlab.com/gitlab-org/gitlabktl:`. - -For a full inventory of available `gitlabktl` versions, see the `gitlabktl` project's -[container registry](https://gitlab.com/gitlab-org/gitlabktl/container_registry). +This feature was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/6) in GitLab 14.3 and [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86267) in GitLab 15.0. diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 2a1e6d99297..ebc0e417393 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -124,6 +124,7 @@ module API expose :printing_merge_request_link_enabled expose :merge_method expose :squash_option + expose :enforce_auth_checks_on_uploads expose :suggestion_commit_message expose :merge_commit_template expose :squash_commit_template diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 3fa20378b19..9a191d98913 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -40,6 +40,7 @@ module API optional :emails_disabled, type: Boolean, desc: 'Disable email notifications' optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis' optional :warn_about_potentially_unwanted_characters, type: Boolean, desc: 'Warn about Potentially Unwanted Characters' + optional :enforce_auth_checks_on_uploads, type: Boolean, desc: 'Enforce auth check on uploads' optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project' optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push' optional :remove_source_branch_after_merge, type: Boolean, desc: 'Remove the source branch by default after merge' @@ -173,6 +174,7 @@ module API :service_desk_enabled, :keep_latest_artifact, :mr_default_target_self, + :enforce_auth_checks_on_uploads, # TODO: remove in API v5, replaced by *_access_level :issues_enabled, diff --git a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml deleted file mode 100644 index 55648437191..00000000000 --- a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml +++ /dev/null @@ -1,35 +0,0 @@ -# To contribute improvements to CI/CD templates, please follow the Development guide at: -# https://docs.gitlab.com/ee/development/cicd/templates.html -# This specific template is located at: -# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml - -# GitLab Serverless template - -image: alpine:latest - -stages: - - build - - test - - deploy - -.serverless:build:image: - image: registry.gitlab.com/gitlab-org/gitlabktl:latest - stage: build - script: /usr/bin/gitlabktl app build - -.serverless:deploy:image: - image: registry.gitlab.com/gitlab-org/gitlabktl:latest - stage: deploy - environment: development - script: /usr/bin/gitlabktl app deploy - -.serverless:build:functions: - image: registry.gitlab.com/gitlab-org/gitlabktl:latest - stage: build - script: /usr/bin/gitlabktl serverless build - -.serverless:deploy:functions: - image: registry.gitlab.com/gitlab-org/gitlabktl:latest - stage: deploy - environment: development - script: /usr/bin/gitlabktl serverless deploy diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml index 14c7fa1dd26..581254ac860 100644 --- a/lib/gitlab/import_export/project/import_export.yml +++ b/lib/gitlab/import_export/project/import_export.yml @@ -753,6 +753,7 @@ excluded_attributes: - :compliance_framework_setting - :show_default_award_emojis - :warn_about_potentially_unwanted_characters + - :enforce_auth_checks_on_uploads - :services - :exported_protected_branches - :repository_size_limit diff --git a/lib/sidebars/projects/menus/infrastructure_menu.rb b/lib/sidebars/projects/menus/infrastructure_menu.rb index 7bd9ac91efa..a98cc20d51a 100644 --- a/lib/sidebars/projects/menus/infrastructure_menu.rb +++ b/lib/sidebars/projects/menus/infrastructure_menu.rb @@ -9,7 +9,6 @@ module Sidebars return false unless context.project.feature_available?(:operations, context.current_user) add_item(kubernetes_menu_item) - add_item(serverless_menu_item) add_item(terraform_menu_item) add_item(google_cloud_menu_item) @@ -63,19 +62,6 @@ module Sidebars auto_devops_help_path: help_page_path('topics/autodevops/index.md') } } end - def serverless_menu_item - unless Feature.enabled?(:deprecated_serverless, context.project, default_enabled: :yaml, type: :ops) && can?(context.current_user, :read_cluster, context.project) - return ::Sidebars::NilMenuItem.new(item_id: :serverless) - end - - ::Sidebars::MenuItem.new( - title: _('Serverless platform'), - link: project_serverless_functions_path(context.project), - active_routes: { controller: :functions }, - item_id: :serverless - ) - end - def terraform_menu_item unless can?(context.current_user, :read_terraform_state, context.project) return ::Sidebars::NilMenuItem.new(item_id: :terraform) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8a2f5120111..923d72b9f20 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -21221,9 +21221,6 @@ msgstr "" msgid "Invited" msgstr "" -msgid "Invocations" -msgstr "" - msgid "IrkerService|Channels and users separated by whitespaces. %{recipients_docs_link}" msgstr "" @@ -23157,9 +23154,6 @@ msgstr "" msgid "Loading files, directories, and submodules in the path %{path} for commit reference %{ref}" msgstr "" -msgid "Loading functions timed out. Please reload the page to try again." -msgstr "" - msgid "Loading more" msgstr "" @@ -34746,78 +34740,6 @@ msgstr "" msgid "Server version" msgstr "" -msgid "Serverless" -msgstr "" - -msgid "Serverless platform" -msgstr "" - -msgid "ServerlessDetails|Configure cluster." -msgstr "" - -msgid "ServerlessDetails|Function invocation metrics require the Prometheus cluster integration." -msgstr "" - -msgid "ServerlessDetails|Invocation metrics loading or not available at this time." -msgstr "" - -msgid "ServerlessDetails|Invocations" -msgstr "" - -msgid "ServerlessDetails|Kubernetes Pods" -msgstr "" - -msgid "ServerlessDetails|More information" -msgstr "" - -msgid "ServerlessDetails|No pods loaded at this time." -msgstr "" - -msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity." -msgstr "" - -msgid "ServerlessDetails|pod in use" -msgstr "" - -msgid "ServerlessDetails|pods in use" -msgstr "" - -msgid "ServerlessURL|Copy URL" -msgstr "" - -msgid "Serverless|Getting started with serverless" -msgstr "" - -msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available." -msgstr "" - -msgid "Serverless|Learn more about Serverless" -msgstr "" - -msgid "Serverless|No functions available" -msgstr "" - -msgid "Serverless|Serverless was %{linkStart}deprecated%{linkEnd} in GitLab 14.3." -msgstr "" - -msgid "Serverless|Serverless was %{postLinkStart}deprecated%{postLinkEnd}. But if you opt to use it, you must install Knative in your Kubernetes cluster first. %{linkStart}Learn more.%{linkEnd}" -msgstr "" - -msgid "Serverless|The deploy job has not finished." -msgstr "" - -msgid "Serverless|The functions listed in the %{startTag}serverless.yml%{endTag} file don't match the namespace of your cluster." -msgstr "" - -msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:" -msgstr "" - -msgid "Serverless|Your %{startTag}.gitlab-ci.yml%{endTag} file is not properly configured." -msgstr "" - -msgid "Serverless|Your repository does not have a corresponding %{startTag}serverless.yml%{endTag} file." -msgstr "" - msgid "Service" msgstr "" diff --git a/spec/controllers/groups/uploads_controller_spec.rb b/spec/controllers/groups/uploads_controller_spec.rb index 7dafb813545..8fcc3a7fccf 100644 --- a/spec/controllers/groups/uploads_controller_spec.rb +++ b/spec/controllers/groups/uploads_controller_spec.rb @@ -35,6 +35,169 @@ RSpec.describe Groups::UploadsController do end end + describe "GET #show" do + let(:filename) { "rails_sample.jpg" } + let(:user) { create(:user) } + let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') } + let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') } + let(:secret) { FileUploader.generate_secret } + let(:uploader_class) { FileUploader } + + let(:upload_service) do + UploadService.new(model, jpg, uploader_class).execute + end + + let(:show_upload) do + get :show, params: params.merge(secret: secret, filename: filename) + end + + before do + allow(FileUploader).to receive(:generate_secret).and_return(secret) + + allow_next_instance_of(FileUploader) do |instance| + allow(instance).to receive(:image?).and_return(true) + end + + upload_service + end + + context 'when the group is public' do + before do + model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC) + end + + context "when not signed in" do + context "enforce_auth_checks_on_uploads feature flag" do + context "with flag enabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: true) + end + + it "responds with appropriate status" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context "with flag disabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user doesn't have access to the model" do + context "enforce_auth_checks_on_uploads feature flag" do + context "with flag enabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: true) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + context "with flag disabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + end + + context 'when the group is private' do + before do + model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE) + end + + context "when not signed in" do + context "enforce_auth_checks_on_uploads feature flag" do + context "with flag enabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: true) + end + + it "responds with appropriate status" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context "with flag disabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user doesn't have access to the model" do + context "enforce_auth_checks_on_uploads feature flag" do + context "with flag enabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: true) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + context "with flag disabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + end + end + def post_authorize(verified: true) request.headers.merge!(workhorse_internal_api_request_header) if verified diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb deleted file mode 100644 index f8cee09006c..00000000000 --- a/spec/controllers/projects/serverless/functions_controller_spec.rb +++ /dev/null @@ -1,341 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Projects::Serverless::FunctionsController do - include KubernetesHelpers - include ReactiveCachingHelpers - - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) } - let(:service) { cluster.platform_kubernetes } - let(:environment) { create(:environment, project: project) } - let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) } - let(:knative_services_finder) { environment.knative_services_finder } - let(:function_description) { 'A serverless function' } - let(:function_name) { 'some-function-name' } - let(:knative_stub_options) do - { namespace: namespace.namespace, name: function_name, description: function_description } - end - - let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } - - let(:namespace) do - create(:cluster_kubernetes_namespace, - cluster: cluster, - cluster_project: cluster.cluster_project, - project: cluster.cluster_project.project, - environment: environment) - end - - before do - project.add_maintainer(user) - sign_in(user) - end - - def params(opts = {}) - opts.reverse_merge(namespace_id: project.namespace.to_param, - project_id: project.to_param) - end - - shared_examples_for 'behind :deprecated_serverless feature flag' do - before do - stub_feature_flags(deprecated_serverless: false) - end - - it 'returns 404' do - action - expect(response).to have_gitlab_http_status(:not_found) - end - end - - describe 'GET #index' do - let(:expected_json) { { 'knative_installed' => knative_state, 'functions' => functions } } - - it_behaves_like 'behind :deprecated_serverless feature flag' do - let(:action) { get :index, params: params({ format: :json }) } - end - - context 'when cache is being read' do - let(:knative_state) { 'checking' } - let(:functions) { [] } - - before do - get :index, params: params({ format: :json }) - end - - it 'returns checking' do - expect(json_response).to eq expected_json - end - - it { expect(response).to have_gitlab_http_status(:ok) } - end - - context 'when cache is ready' do - let(:knative_state) { true } - - before do - allow(Clusters::KnativeServicesFinder) - .to receive(:new) - .and_return(knative_services_finder) - synchronous_reactive_cache(knative_services_finder) - stub_kubeclient_service_pods( - kube_response({ "kind" => "PodList", "items" => [] }), - namespace: namespace.namespace - ) - end - - context 'when no functions were found' do - let(:functions) { [] } - - before do - stub_kubeclient_knative_services( - namespace: namespace.namespace, - response: kube_response({ "kind" => "ServiceList", "items" => [] }) - ) - get :index, params: params({ format: :json }) - end - - it 'returns checking' do - expect(json_response).to eq expected_json - end - - it { expect(response).to have_gitlab_http_status(:ok) } - end - - context 'when functions were found' do - let(:functions) { [{}, {}] } - - before do - stub_kubeclient_knative_services(namespace: namespace.namespace, cluster_id: cluster.id, name: function_name) - end - - it 'returns functions' do - get :index, params: params({ format: :json }) - expect(json_response["functions"]).not_to be_empty - end - - it 'filters out the functions whose cluster the user does not have permission to read' do - allow(controller).to receive(:can?).and_return(true) - expect(controller).to receive(:can?).with(user, :read_cluster, cluster).and_return(false) - - get :index, params: params({ format: :json }) - - expect(json_response["functions"]).to be_empty - end - - it 'returns a successful response status' do - get :index, params: params({ format: :json }) - expect(response).to have_gitlab_http_status(:ok) - end - - context 'when there is serverless domain for a cluster' do - let!(:serverless_domain_cluster) do - create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id) - end - - it 'returns JSON with function details with serverless domain URL' do - get :index, params: params({ format: :json }) - expect(response).to have_gitlab_http_status(:ok) - - expect(json_response["functions"]).not_to be_empty - - expect(json_response["functions"]).to all( - include( - 'url' => "https://#{function_name}-#{serverless_domain_cluster.uuid[0..1]}a1#{serverless_domain_cluster.uuid[2..-3]}f2#{serverless_domain_cluster.uuid[-2..]}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}" - ) - ) - end - end - - context 'when there is no serverless domain for a cluster' do - it 'keeps function URL as it was' do - expect(::Serverless::Domain).not_to receive(:new) - - get :index, params: params({ format: :json }) - expect(response).to have_gitlab_http_status(:ok) - end - end - end - end - end - - describe 'GET #show' do - it_behaves_like 'behind :deprecated_serverless feature flag' do - let(:action) { get :show, params: params({ format: :json, environment_id: "*", id: "foo" }) } - end - - context 'with function that does not exist' do - it 'returns 404' do - get :show, params: params({ format: :json, environment_id: "*", id: "foo" }) - expect(response).to have_gitlab_http_status(:not_found) - end - end - - context 'with valid data', :use_clean_rails_memory_store_caching do - shared_examples 'GET #show with valid data' do - context 'when there is serverless domain for a cluster' do - let!(:serverless_domain_cluster) do - create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id) - end - - it 'returns JSON with function details with serverless domain URL' do - get :show, params: params({ format: :json, environment_id: "*", id: function_name }) - expect(response).to have_gitlab_http_status(:ok) - - expect(json_response).to include( - 'url' => "https://#{function_name}-#{serverless_domain_cluster.uuid[0..1]}a1#{serverless_domain_cluster.uuid[2..-3]}f2#{serverless_domain_cluster.uuid[-2..]}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}" - ) - end - - it 'returns 404 when user does not have permission to read the cluster' do - allow(controller).to receive(:can?).and_return(true) - expect(controller).to receive(:can?).with(user, :read_cluster, cluster).and_return(false) - - get :show, params: params({ format: :json, environment_id: "*", id: function_name }) - - expect(response).to have_gitlab_http_status(:not_found) - end - end - - context 'when there is no serverless domain for a cluster' do - it 'keeps function URL as it was' do - get :show, params: params({ format: :json, environment_id: "*", id: function_name }) - expect(response).to have_gitlab_http_status(:ok) - - expect(json_response).to include( - 'url' => "http://#{function_name}.#{namespace.namespace}.example.com" - ) - end - end - - it 'return json with function details' do - get :show, params: params({ format: :json, environment_id: "*", id: function_name }) - expect(response).to have_gitlab_http_status(:ok) - - expect(json_response).to include( - 'name' => function_name, - 'url' => "http://#{function_name}.#{namespace.namespace}.example.com", - 'description' => function_description, - 'podcount' => 0 - ) - end - end - - context 'on Knative 0.5.0' do - before do - prepare_knative_stubs(knative_05_service(**knative_stub_options)) - end - - include_examples 'GET #show with valid data' - end - - context 'on Knative 0.6.0' do - before do - prepare_knative_stubs(knative_06_service(**knative_stub_options)) - end - - include_examples 'GET #show with valid data' - end - - context 'on Knative 0.7.0' do - before do - prepare_knative_stubs(knative_07_service(**knative_stub_options)) - end - - include_examples 'GET #show with valid data' - end - - context 'on Knative 0.9.0' do - before do - prepare_knative_stubs(knative_09_service(**knative_stub_options)) - end - - include_examples 'GET #show with valid data' - end - end - end - - describe 'GET #metrics' do - it_behaves_like 'behind :deprecated_serverless feature flag' do - let(:action) { get :metrics, params: params({ format: :json, environment_id: "*", id: "foo" }) } - end - - context 'invalid data' do - it 'has a bad function name' do - get :metrics, params: params({ format: :json, environment_id: "*", id: "foo" }) - expect(response).to have_gitlab_http_status(:no_content) - end - end - end - - describe 'GET #index with data', :use_clean_rails_memory_store_caching do - shared_examples 'GET #index with data' do - it 'has data' do - get :index, params: params({ format: :json }) - - expect(response).to have_gitlab_http_status(:ok) - - expect(json_response).to match({ - 'knative_installed' => 'checking', - 'functions' => [ - a_hash_including( - 'name' => function_name, - 'url' => "http://#{function_name}.#{namespace.namespace}.example.com", - 'description' => function_description - ) - ] - }) - end - - it 'has data in html' do - get :index, params: params - - expect(response).to have_gitlab_http_status(:ok) - end - end - - context 'on Knative 0.5.0' do - before do - prepare_knative_stubs(knative_05_service(**knative_stub_options)) - end - - include_examples 'GET #index with data' - end - - context 'on Knative 0.6.0' do - before do - prepare_knative_stubs(knative_06_service(**knative_stub_options)) - end - - include_examples 'GET #index with data' - end - - context 'on Knative 0.7.0' do - before do - prepare_knative_stubs(knative_07_service(**knative_stub_options)) - end - - include_examples 'GET #index with data' - end - - context 'on Knative 0.9.0' do - before do - prepare_knative_stubs(knative_09_service(**knative_stub_options)) - end - - include_examples 'GET #index with data' - end - end - - def prepare_knative_stubs(knative_service) - stub_kubeclient_service_pods - stub_reactive_cache(knative_services_finder, - { - services: [knative_service], - pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] - }, - *knative_services_finder.cache_args) - end -end diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index c008c7253d8..6d2db25ade2 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -54,6 +54,241 @@ RSpec.describe Projects::UploadsController do end end + describe "GET #show" do + let(:filename) { "rails_sample.jpg" } + let(:user) { create(:user) } + let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') } + let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') } + let(:secret) { FileUploader.generate_secret } + let(:uploader_class) { FileUploader } + + let(:upload_service) do + UploadService.new(model, jpg, uploader_class).execute + end + + let(:show_upload) do + get :show, params: params.merge(secret: secret, filename: filename) + end + + before do + allow(FileUploader).to receive(:generate_secret).and_return(secret) + + allow_next_instance_of(FileUploader) do |instance| + allow(instance).to receive(:image?).and_return(true) + end + + upload_service + end + + context 'when project is private do' do + before do + model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE) + end + + context "when not signed in" do + context "enforce_auth_checks_on_uploads feature flag" do + context "with flag enabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: true) + end + + context 'when the project has setting enforce_auth_checks_on_uploads true' do + before do + model.update!(enforce_auth_checks_on_uploads: true) + end + + it "responds with status 302" do + show_upload + + expect(response).to have_gitlab_http_status(:redirect) + end + end + + context 'when the project has setting enforce_auth_checks_on_uploads false' do + before do + model.update!(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + context "with flag disabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user doesn't have access to the model" do + context "enforce_auth_checks_on_uploads feature flag" do + context "with flag enabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: true) + end + + context 'when the project has setting enforce_auth_checks_on_uploads true' do + before do + model.update!(enforce_auth_checks_on_uploads: true) + end + + it "responds with status 404" do + show_upload + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when the project has setting enforce_auth_checks_on_uploads false' do + before do + model.update!(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + + context "with flag disabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + end + + context 'when project is public' do + before do + model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC) + end + + context "when not signed in" do + context "enforce_auth_checks_on_uploads feature flag" do + context "with flag enabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: true) + end + + context 'when the project has setting enforce_auth_checks_on_uploads true' do + before do + model.update!(enforce_auth_checks_on_uploads: true) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when the project has setting enforce_auth_checks_on_uploads false' do + before do + model.update!(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + context "with flag disabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user doesn't have access to the model" do + context "enforce_auth_checks_on_uploads feature flag" do + context "with flag enabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: true) + end + + context 'when the project has setting enforce_auth_checks_on_uploads true' do + before do + model.update!(enforce_auth_checks_on_uploads: true) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when the project has setting enforce_auth_checks_on_uploads false' do + before do + model.update!(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + + context "with flag disabled" do + before do + stub_feature_flags(enforce_auth_checks_on_uploads: false) + end + + it "responds with status 200" do + show_upload + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + end + end + end + def post_authorize(verified: true) request.headers.merge!(workhorse_internal_api_request_header) if verified diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index f4f3530639b..a69317032ef 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -863,7 +863,8 @@ RSpec.describe ProjectsController do id: project.path, project: { project_setting_attributes: { - show_default_award_emojis: boolean_value + show_default_award_emojis: boolean_value, + enforce_auth_checks_on_uploads: boolean_value } } } @@ -871,6 +872,7 @@ RSpec.describe ProjectsController do project.reload expect(project.show_default_award_emojis?).to eq(result) + expect(project.enforce_auth_checks_on_uploads?).to eq(result) end end end diff --git a/spec/features/monitor_sidebar_link_spec.rb b/spec/features/monitor_sidebar_link_spec.rb index fcef0fa0eff..3c59cd65cdb 100644 --- a/spec/features/monitor_sidebar_link_spec.rb +++ b/spec/features/monitor_sidebar_link_spec.rb @@ -45,7 +45,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures do expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project)) expect(page).not_to have_link('Error Tracking', href: project_error_tracking_index_path(project)) expect(page).not_to have_link('Product Analytics', href: project_product_analytics_path(project)) - expect(page).not_to have_link('Serverless', href: project_serverless_functions_path(project)) expect(page).not_to have_link('Logs', href: project_logs_path(project)) expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project)) end @@ -79,7 +78,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures do expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project)) expect(page).not_to have_link('Error Tracking', href: project_error_tracking_index_path(project)) expect(page).not_to have_link('Product Analytics', href: project_product_analytics_path(project)) - expect(page).not_to have_link('Serverless', href: project_serverless_functions_path(project)) expect(page).not_to have_link('Logs', href: project_logs_path(project)) expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project)) end @@ -98,7 +96,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures do expect(page).to have_link('Product Analytics', href: project_product_analytics_path(project)) expect(page).not_to have_link('Alerts', href: project_alert_management_index_path(project)) - expect(page).not_to have_link('Serverless', href: project_serverless_functions_path(project)) expect(page).not_to have_link('Logs', href: project_logs_path(project)) expect(page).not_to have_link('Kubernetes', href: project_clusters_path(project)) end @@ -117,7 +114,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures do expect(page).to have_link('Error Tracking', href: project_error_tracking_index_path(project)) expect(page).to have_link('Product Analytics', href: project_product_analytics_path(project)) expect(page).to have_link('Logs', href: project_logs_path(project)) - expect(page).to have_link('Serverless', href: project_serverless_functions_path(project)) expect(page).to have_link('Kubernetes', href: project_clusters_path(project)) end @@ -134,7 +130,6 @@ RSpec.describe 'Monitor dropdown sidebar', :aggregate_failures do expect(page).to have_link('Environments', href: project_environments_path(project)) expect(page).to have_link('Error Tracking', href: project_error_tracking_index_path(project)) expect(page).to have_link('Product Analytics', href: project_product_analytics_path(project)) - expect(page).to have_link('Serverless', href: project_serverless_functions_path(project)) expect(page).to have_link('Logs', href: project_logs_path(project)) expect(page).to have_link('Kubernetes', href: project_clusters_path(project)) end diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb deleted file mode 100644 index db8c2a24f2f..00000000000 --- a/spec/features/projects/serverless/functions_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Functions', :js do - include KubernetesHelpers - include ReactiveCachingHelpers - - let(:project) { create(:project, :repository) } - let(:user) { create(:user) } - - before do - project.add_maintainer(user) - gitlab_sign_in(user) - end - - shared_examples "it's missing knative installation" do - before do - functions_finder = Projects::Serverless::FunctionsFinder.new(project) - visit project_serverless_functions_path(project) - allow(Projects::Serverless::FunctionsFinder) - .to receive(:new) - .and_return(functions_finder) - synchronous_reactive_cache(functions_finder) - end - - it 'sees an empty state require Knative installation' do - expect(page).to have_selector('.empty-state') - end - end - - context 'when user does not have a cluster and visits the serverless page' do - it_behaves_like "it's missing knative installation" - end - - context 'when the user does have a cluster and visits the serverless page' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - - it_behaves_like "it's missing knative installation" - end - - context 'when the user has a cluster and knative installed and visits the serverless page', :kubeclient do - let(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) } - let(:service) { cluster.platform_kubernetes } - let(:environment) { create(:environment, project: project) } - let!(:deployment) { create(:deployment, :success, cluster: cluster, environment: environment) } - let(:knative_services_finder) { environment.knative_services_finder } - let(:namespace) do - create(:cluster_kubernetes_namespace, - cluster: cluster, - project: cluster.cluster_project.project, - environment: environment) - end - - before do - allow(Clusters::KnativeServicesFinder) - .to receive(:new) - .and_return(knative_services_finder) - synchronous_reactive_cache(knative_services_finder) - stub_kubeclient_knative_services(stub_get_services_options) - stub_kubeclient_service_pods(nil, namespace: namespace.namespace) - visit project_serverless_functions_path(project) - end - - context 'when there are no functions' do - let(:stub_get_services_options) do - { - namespace: namespace.namespace, - response: kube_response({ "kind" => "ServiceList", "items" => [] }) - } - end - - it 'sees an empty listing of serverless functions' do - expect(page).to have_selector('.empty-state') - expect(page).not_to have_selector('.content-list') - end - end - - context 'when there are functions' do - let(:stub_get_services_options) { { namespace: namespace.namespace } } - - it 'does not see an empty listing of serverless functions' do - expect(page).not_to have_selector('.empty-state') - expect(page).to have_selector('.content-list') - end - end - end -end diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb deleted file mode 100644 index 9b58da2e398..00000000000 --- a/spec/finders/projects/serverless/functions_finder_spec.rb +++ /dev/null @@ -1,185 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Projects::Serverless::FunctionsFinder do - include KubernetesHelpers - include PrometheusHelpers - include ReactiveCachingHelpers - - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) } - let(:service) { cluster.platform_kubernetes } - let(:environment) { create(:environment, project: project) } - let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) } - let(:knative_services_finder) { environment.knative_services_finder } - - let(:namespace) do - create(:cluster_kubernetes_namespace, - cluster: cluster, - project: project, - environment: environment) - end - - before do - project.add_maintainer(user) - end - - describe '#knative_installed' do - context 'when environment does not exist yet' do - shared_examples 'before first deployment' do - let(:service) { cluster.platform_kubernetes } - let(:deployment) { nil } - - it 'returns true if Knative is installed on cluster' do - stub_kubeclient_discover_knative_found(service.api_url) - function_finder = described_class.new(project) - synchronous_reactive_cache(function_finder) - - expect(function_finder.knative_installed).to be true - end - - it 'returns false if Knative is not installed on cluster' do - stub_kubeclient_discover_knative_not_found(service.api_url) - function_finder = described_class.new(project) - synchronous_reactive_cache(function_finder) - - expect(function_finder.knative_installed).to be false - end - end - - context 'when project level cluster is present and enabled' do - it_behaves_like 'before first deployment' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp, enabled: true) } - let(:project) { cluster.project } - end - end - - context 'when group level cluster is present and enabled' do - it_behaves_like 'before first deployment' do - let(:cluster) { create(:cluster, :group, :provided_by_gcp, enabled: true) } - let(:project) { create(:project, group: cluster.groups.first) } - end - end - - context 'when instance level cluster is present and enabled' do - it_behaves_like 'before first deployment' do - let(:project) { create(:project) } - let(:cluster) { create(:cluster, :instance, :provided_by_gcp, enabled: true) } - end - end - - context 'when project level cluster is present, but disabled' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp, enabled: false) } - let(:project) { cluster.project } - let(:service) { cluster.platform_kubernetes } - let(:deployment) { nil } - - it 'returns false even if Knative is installed on cluster' do - stub_kubeclient_discover_knative_found(service.api_url) - function_finder = described_class.new(project) - synchronous_reactive_cache(function_finder) - - expect(function_finder.knative_installed).to be false - end - end - end - - context 'when reactive_caching is still fetching data' do - it 'returns "checking"' do - expect(described_class.new(project).knative_installed).to eq 'checking' - end - end - - context 'when reactive_caching has finished' do - before do - allow(Clusters::KnativeServicesFinder) - .to receive(:new) - .and_return(knative_services_finder) - synchronous_reactive_cache(knative_services_finder) - end - - context 'when knative is not installed' do - it 'returns false' do - stub_kubeclient_discover_knative_not_found(service.api_url) - - expect(described_class.new(project).knative_installed).to eq false - end - end - - context 'reactive_caching is finished and knative is installed' do - it 'returns true' do - stub_kubeclient_knative_services(namespace: namespace.namespace) - stub_kubeclient_service_pods(nil, namespace: namespace.namespace) - - expect(described_class.new(project).knative_installed).to be true - end - end - end - end - - describe 'retrieve data from knative' do - context 'does not have knative installed' do - it { expect(described_class.new(project).execute).to be_empty } - end - - context 'has knative installed' do - let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } - let(:finder) { described_class.new(project) } - - it 'there are no functions' do - expect(finder.execute).to be_empty - end - - it 'there are functions', :use_clean_rails_memory_store_caching do - stub_kubeclient_service_pods - stub_reactive_cache(knative_services_finder, - { - services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], - pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] - }, - *knative_services_finder.cache_args) - - expect(finder.execute).not_to be_empty - end - - it 'has a function', :use_clean_rails_memory_store_caching do - stub_kubeclient_service_pods - stub_reactive_cache(knative_services_finder, - { - services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], - pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] - }, - *knative_services_finder.cache_args) - - result = finder.service(cluster.environment_scope, cluster.project.name) - expect(result).to be_present - expect(result.name).to be_eql(cluster.project.name) - end - - it 'has metrics', :use_clean_rails_memory_store_caching do - end - end - - context 'has prometheus' do - let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) } - let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } - let!(:prometheus) { create(:clusters_integrations_prometheus, cluster: cluster) } - let(:finder) { described_class.new(project) } - - before do - allow(Gitlab::Prometheus::Adapter).to receive(:new).and_return(double(prometheus_adapter: prometheus_adapter)) - allow(prometheus_adapter).to receive(:query).and_return(prometheus_empty_body('matrix')) - end - - it 'is available' do - expect(finder.has_prometheus?("*")).to be true - end - - it 'has query data' do - expect(finder.invocation_metrics("*", cluster.project.name)).not_to be_nil - end - end - end -end diff --git a/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap b/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap deleted file mode 100644 index 0f4dfdf8a75..00000000000 --- a/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap +++ /dev/null @@ -1,22 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EmptyStateComponent should render content 1`] = ` -"

-
-
\\"\\"
-
-
-
-

- Getting started with serverless -

-

Serverless was deprecated. But if you opt to use it, you must install Knative in your Kubernetes cluster first. Learn more. -

-
- - -
-
-
-
" -`; diff --git a/spec/frontend/serverless/components/area_spec.js b/spec/frontend/serverless/components/area_spec.js deleted file mode 100644 index 05c9ee44307..00000000000 --- a/spec/frontend/serverless/components/area_spec.js +++ /dev/null @@ -1,121 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Area from '~/serverless/components/area.vue'; -import { mockNormalizedMetrics } from '../mock_data'; - -describe('Area component', () => { - const mockWidgets = 'mockWidgets'; - const mockGraphData = mockNormalizedMetrics; - let areaChart; - - beforeEach(() => { - areaChart = shallowMount(Area, { - propsData: { - graphData: mockGraphData, - containerWidth: 0, - }, - slots: { - default: mockWidgets, - }, - }); - }); - - afterEach(() => { - areaChart.destroy(); - }); - - it('renders chart title', () => { - expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title); - }); - - it('contains graph widgets from slot', () => { - expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets); - }); - - describe('methods', () => { - describe('formatTooltipText', () => { - const mockDate = mockNormalizedMetrics.queries[0].result[0].values[0].time; - const generateSeriesData = (type) => ({ - seriesData: [ - { - componentSubType: type, - value: [mockDate, 4], - }, - ], - value: mockDate, - }); - - describe('series is of line type', () => { - beforeEach(() => { - areaChart.vm.formatTooltipText(generateSeriesData('line')); - }); - - it('formats tooltip title', () => { - expect(areaChart.vm.tooltipPopoverTitle).toBe('28 Feb 2019, 11:11AM'); - }); - - it('formats tooltip content', () => { - expect(areaChart.vm.tooltipPopoverContent).toBe('Invocations (requests): 4'); - }); - }); - - it('verify default interval value of 1', () => { - expect(areaChart.vm.getInterval).toBe(1); - }); - }); - - describe('onResize', () => { - const mockWidth = 233; - - beforeEach(() => { - jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({ - width: mockWidth, - })); - areaChart.vm.onResize(); - }); - - it('sets area chart width', () => { - expect(areaChart.vm.width).toBe(mockWidth); - }); - }); - }); - - describe('computed', () => { - describe('chartData', () => { - it('utilizes all data points', () => { - expect(Object.keys(areaChart.vm.chartData)).toEqual(['requests']); - expect(areaChart.vm.chartData.requests.length).toBe(2); - }); - - it('creates valid data', () => { - const data = areaChart.vm.chartData.requests; - - expect( - data.filter( - (datum) => new Date(datum.time).getTime() > 0 && typeof datum.value === 'number', - ).length, - ).toBe(data.length); - }); - }); - - describe('generateSeries', () => { - it('utilizes correct time data', () => { - expect(areaChart.vm.generateSeries.data).toEqual([ - ['2019-02-28T11:11:38.756Z', 0], - ['2019-02-28T11:12:38.756Z', 0], - ]); - }); - }); - - describe('xAxisLabel', () => { - it('constructs a label for the chart x-axis', () => { - expect(areaChart.vm.xAxisLabel).toBe('invocations / minute'); - }); - }); - - describe('yAxisLabel', () => { - it('constructs a label for the chart y-axis', () => { - expect(areaChart.vm.yAxisLabel).toBe('Invocations (requests)'); - }); - }); - }); -}); diff --git a/spec/frontend/serverless/components/empty_state_spec.js b/spec/frontend/serverless/components/empty_state_spec.js deleted file mode 100644 index d63882c2a6d..00000000000 --- a/spec/frontend/serverless/components/empty_state_spec.js +++ /dev/null @@ -1,25 +0,0 @@ -import { GlEmptyState, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import EmptyStateComponent from '~/serverless/components/empty_state.vue'; -import { createStore } from '~/serverless/store'; - -describe('EmptyStateComponent', () => { - let wrapper; - - beforeEach(() => { - const store = createStore({ - clustersPath: '/clusters', - helpPath: '/help', - emptyImagePath: '/image.svg', - }); - wrapper = shallowMount(EmptyStateComponent, { store, stubs: { GlEmptyState, GlSprintf } }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('should render content', () => { - expect(wrapper.html()).toMatchSnapshot(); - }); -}); diff --git a/spec/frontend/serverless/components/environment_row_spec.js b/spec/frontend/serverless/components/environment_row_spec.js deleted file mode 100644 index 944283136d0..00000000000 --- a/spec/frontend/serverless/components/environment_row_spec.js +++ /dev/null @@ -1,68 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import environmentRowComponent from '~/serverless/components/environment_row.vue'; - -import { translate } from '~/serverless/utils'; -import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; - -const createComponent = (env, envName) => - shallowMount(environmentRowComponent, { - propsData: { env, envName }, - }).vm; - -describe('environment row component', () => { - describe('default global cluster case', () => { - let vm; - - beforeEach(() => { - vm = createComponent(translate(mockServerlessFunctions.functions)['*'], '*'); - }); - - afterEach(() => vm.$destroy()); - - it('has the correct envId', () => { - expect(vm.envId).toEqual('env-global'); - }); - - it('is open by default', () => { - expect(vm.isOpenClass).toEqual({ 'is-open': true }); - }); - - it('generates correct output', () => { - expect(vm.$el.id).toEqual('env-global'); - expect(vm.$el.classList.contains('is-open')).toBe(true); - expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('*'); - }); - - it('opens and closes correctly', () => { - expect(vm.isOpen).toBe(true); - - vm.toggleOpen(); - - expect(vm.isOpen).toBe(false); - }); - }); - - describe('default named cluster case', () => { - let vm; - - beforeEach(() => { - vm = createComponent(translate(mockServerlessFunctionsDiffEnv.functions).test, 'test'); - }); - - afterEach(() => vm.$destroy()); - - it('has the correct envId', () => { - expect(vm.envId).toEqual('env-test'); - }); - - it('is open by default', () => { - expect(vm.isOpenClass).toEqual({ 'is-open': true }); - }); - - it('generates correct output', () => { - expect(vm.$el.id).toEqual('env-test'); - expect(vm.$el.classList.contains('is-open')).toBe(true); - expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('test'); - }); - }); -}); diff --git a/spec/frontend/serverless/components/function_details_spec.js b/spec/frontend/serverless/components/function_details_spec.js deleted file mode 100644 index 0c9b2498589..00000000000 --- a/spec/frontend/serverless/components/function_details_spec.js +++ /dev/null @@ -1,100 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -import Vuex from 'vuex'; - -import functionDetailsComponent from '~/serverless/components/function_details.vue'; -import { createStore } from '~/serverless/store'; - -describe('functionDetailsComponent', () => { - let component; - let store; - - beforeEach(() => { - Vue.use(Vuex); - - store = createStore({ clustersPath: '/clusters', helpPath: '/help' }); - }); - - afterEach(() => { - component.vm.$destroy(); - }); - - describe('Verify base functionality', () => { - const serviceStub = { - name: 'test', - description: 'a description', - environment: '*', - url: 'http://service.com/test', - namespace: 'test-ns', - podcount: 0, - metricsUrl: '/metrics', - }; - - it('has a name, description, URL, and no pods loaded', () => { - component = shallowMount(functionDetailsComponent, { - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - }, - }); - - expect( - component.vm.$el.querySelector('.serverless-function-name').innerHTML.trim(), - ).toContain('test'); - - expect( - component.vm.$el.querySelector('.serverless-function-description').innerHTML.trim(), - ).toContain('a description'); - - expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain( - 'No pods loaded at this time.', - ); - }); - - it('has a pods loaded', () => { - serviceStub.podcount = 1; - - component = shallowMount(functionDetailsComponent, { - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - }, - }); - - expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('1 pod in use'); - }); - - it('has multiple pods loaded', () => { - serviceStub.podcount = 3; - - component = shallowMount(functionDetailsComponent, { - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - }, - }); - - expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('3 pods in use'); - }); - - it('can support a missing description', () => { - serviceStub.description = null; - - component = shallowMount(functionDetailsComponent, { - store, - propsData: { - func: serviceStub, - hasPrometheus: false, - }, - }); - - expect( - component.vm.$el.querySelector('.serverless-function-description').querySelector('div') - .innerHTML.length, - ).toEqual(0); - }); - }); -}); diff --git a/spec/frontend/serverless/components/function_row_spec.js b/spec/frontend/serverless/components/function_row_spec.js deleted file mode 100644 index 081edd33b3b..00000000000 --- a/spec/frontend/serverless/components/function_row_spec.js +++ /dev/null @@ -1,34 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import functionRowComponent from '~/serverless/components/function_row.vue'; -import Timeago from '~/vue_shared/components/time_ago_tooltip.vue'; - -import { mockServerlessFunction } from '../mock_data'; - -describe('functionRowComponent', () => { - let wrapper; - - const createComponent = (func) => { - wrapper = shallowMount(functionRowComponent, { - propsData: { func }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - it('Parses the function details correctly', () => { - createComponent(mockServerlessFunction); - - expect(wrapper.find('b').text()).toBe(mockServerlessFunction.name); - expect(wrapper.find('span').text()).toBe(mockServerlessFunction.image); - expect(wrapper.find(Timeago).attributes('time')).not.toBe(null); - }); - - it('handles clicks correctly', () => { - createComponent(mockServerlessFunction); - const { vm } = wrapper; - - expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row - }); -}); diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js deleted file mode 100644 index 846fd63e918..00000000000 --- a/spec/frontend/serverless/components/functions_spec.js +++ /dev/null @@ -1,86 +0,0 @@ -import { GlLoadingIcon, GlAlert, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; -import AxiosMockAdapter from 'axios-mock-adapter'; -import Vuex from 'vuex'; -import { TEST_HOST } from 'helpers/test_constants'; -import axios from '~/lib/utils/axios_utils'; -import EmptyState from '~/serverless/components/empty_state.vue'; -import EnvironmentRow from '~/serverless/components/environment_row.vue'; -import functionsComponent from '~/serverless/components/functions.vue'; -import { createStore } from '~/serverless/store'; -import { mockServerlessFunctions } from '../mock_data'; - -describe('functionsComponent', () => { - const statusPath = `${TEST_HOST}/statusPath`; - - let component; - let store; - let axiosMock; - - beforeEach(() => { - axiosMock = new AxiosMockAdapter(axios); - axiosMock.onGet(statusPath).reply(200); - - Vue.use(Vuex); - - store = createStore({}); - component = shallowMount(functionsComponent, { store, stubs: { GlSprintf } }); - }); - - afterEach(() => { - component.destroy(); - axiosMock.restore(); - }); - - it('should render deprecation notice', () => { - expect(component.findComponent(GlAlert).text()).toBe( - 'Serverless was deprecated in GitLab 14.3.', - ); - }); - - it('should render empty state when Knative is not installed', async () => { - await store.dispatch('receiveFunctionsSuccess', { knative_installed: false }); - - expect(component.findComponent(EmptyState).exists()).toBe(true); - }); - - it('should render a loading component', async () => { - await store.dispatch('requestFunctionsLoading'); - - expect(component.findComponent(GlLoadingIcon).exists()).toBe(true); - }); - - it('should render empty state when there is no function data', async () => { - await store.dispatch('receiveFunctionsNoDataSuccess', { knative_installed: true }); - - expect( - component.vm.$el - .querySelector('.empty-state, .js-empty-state') - .classList.contains('js-empty-state'), - ).toBe(true); - - expect(component.vm.$el.querySelector('.state-title, .text-center').innerHTML.trim()).toEqual( - 'No functions available', - ); - }); - - it('should render functions and a loader when functions are partially fetched', async () => { - await store.dispatch('receiveFunctionsPartial', { - ...mockServerlessFunctions, - knative_installed: 'checking', - }); - - expect(component.find('.js-functions-wrapper').exists()).toBe(true); - expect(component.find('.js-functions-loader').exists()).toBe(true); - }); - - it('should render the functions list', async () => { - store = createStore({ clustersPath: 'clustersPath', helpPath: 'helpPath', statusPath }); - - await component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions); - - await nextTick(); - expect(component.findComponent(EnvironmentRow).exists()).toBe(true); - }); -}); diff --git a/spec/frontend/serverless/components/missing_prometheus_spec.js b/spec/frontend/serverless/components/missing_prometheus_spec.js deleted file mode 100644 index 1b93fd784e1..00000000000 --- a/spec/frontend/serverless/components/missing_prometheus_spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import { GlButton } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue'; -import { createStore } from '~/serverless/store'; - -describe('missingPrometheusComponent', () => { - let wrapper; - - const createComponent = (missingData) => { - const store = createStore({ clustersPath: '/clusters', helpPath: '/help' }); - - wrapper = shallowMount(missingPrometheusComponent, { store, propsData: { missingData } }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - it('should render missing prometheus message', () => { - createComponent(false); - const { vm } = wrapper; - - expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( - 'Function invocation metrics require the Prometheus cluster integration.', - ); - - expect(wrapper.find(GlButton).attributes('variant')).toBe('success'); - }); - - it('should render no prometheus data message', () => { - createComponent(true); - const { vm } = wrapper; - - expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( - 'Invocation metrics loading or not available at this time.', - ); - }); -}); diff --git a/spec/frontend/serverless/components/pod_box_spec.js b/spec/frontend/serverless/components/pod_box_spec.js deleted file mode 100644 index cf0c14a2cac..00000000000 --- a/spec/frontend/serverless/components/pod_box_spec.js +++ /dev/null @@ -1,22 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import podBoxComponent from '~/serverless/components/pod_box.vue'; - -const createComponent = (count) => - shallowMount(podBoxComponent, { - propsData: { - count, - }, - }).vm; - -describe('podBoxComponent', () => { - it('should render three boxes', () => { - const count = 3; - const vm = createComponent(count); - const rects = vm.$el.querySelectorAll('rect'); - - expect(rects.length).toEqual(3); - expect(parseInt(rects[2].getAttribute('x'), 10)).toEqual(40); - - vm.$destroy(); - }); -}); diff --git a/spec/frontend/serverless/components/url_spec.js b/spec/frontend/serverless/components/url_spec.js deleted file mode 100644 index 8c839577aa0..00000000000 --- a/spec/frontend/serverless/components/url_spec.js +++ /dev/null @@ -1,26 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -import urlComponent from '~/serverless/components/url.vue'; -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; - -const createComponent = (uri) => - shallowMount(Vue.extend(urlComponent), { - propsData: { - uri, - }, - }); - -describe('urlComponent', () => { - it('should render correctly', () => { - const uri = 'http://testfunc.apps.example.com'; - const wrapper = createComponent(uri); - const { vm } = wrapper; - - expect(vm.$el.classList.contains('clipboard-group')).toBe(true); - expect(wrapper.find(ClipboardButton).attributes('text')).toEqual(uri); - - expect(vm.$el.querySelector('[data-testid="url-text-field"]').innerHTML).toContain(uri); - - vm.$destroy(); - }); -}); diff --git a/spec/frontend/serverless/mock_data.js b/spec/frontend/serverless/mock_data.js deleted file mode 100644 index 1816ad62a04..00000000000 --- a/spec/frontend/serverless/mock_data.js +++ /dev/null @@ -1,145 +0,0 @@ -export const mockServerlessFunctions = { - knative_installed: true, - functions: [ - { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'A test service', - image: 'knative-test-container-buildtemplate', - }, - { - name: 'testfunc2', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc2.tm-example.apps.example.com', - description: 'A second test service\nThis one with additional descriptions', - image: 'knative-test-echo-buildtemplate', - }, - ], -}; - -export const mockServerlessFunctionsDiffEnv = { - knative_installed: true, - functions: [ - { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'A test service', - image: 'knative-test-container-buildtemplate', - }, - { - name: 'testfunc2', - namespace: 'tm-example', - environment_scope: 'test', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', - podcount: null, - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc2.tm-example.apps.example.com', - description: 'A second test service\nThis one with additional descriptions', - image: 'knative-test-echo-buildtemplate', - }, - ], -}; - -export const mockServerlessFunction = { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: '3', - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'A test service', - image: 'knative-test-container-buildtemplate', -}; - -export const mockMultilineServerlessFunction = { - name: 'testfunc1', - namespace: 'tm-example', - environment_scope: '*', - cluster_id: 46, - detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', - podcount: '3', - created_at: '2019-02-05T01:01:23Z', - url: 'http://testfunc1.tm-example.apps.example.com', - description: 'testfunc1\nA test service line\\nWith additional services', - image: 'knative-test-container-buildtemplate', -}; - -export const mockMetrics = { - success: true, - last_update: '2019-02-28T19:11:38.926Z', - metrics: { - id: 22, - title: 'Knative function invocations', - required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], - weight: 0, - y_label: 'Invocations', - queries: [ - { - query_range: - 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', - unit: 'requests', - label: 'invocations / minute', - result: [ - { - metric: {}, - values: [ - [1551352298.756, '0'], - [1551352358.756, '0'], - ], - }, - ], - }, - ], - }, -}; - -export const mockNormalizedMetrics = { - id: 22, - title: 'Knative function invocations', - required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], - weight: 0, - y_label: 'Invocations', - queries: [ - { - query_range: - 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', - unit: 'requests', - label: 'invocations / minute', - result: [ - { - metric: {}, - values: [ - { - time: '2019-02-28T11:11:38.756Z', - value: 0, - }, - { - time: '2019-02-28T11:12:38.756Z', - value: 0, - }, - ], - }, - ], - }, - ], -}; diff --git a/spec/frontend/serverless/store/actions_spec.js b/spec/frontend/serverless/store/actions_spec.js deleted file mode 100644 index 5fbecf081a6..00000000000 --- a/spec/frontend/serverless/store/actions_spec.js +++ /dev/null @@ -1,80 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import testAction from 'helpers/vuex_action_helper'; -import axios from '~/lib/utils/axios_utils'; -import statusCodes from '~/lib/utils/http_status'; -import { fetchFunctions, fetchMetrics } from '~/serverless/store/actions'; -import { mockServerlessFunctions, mockMetrics } from '../mock_data'; -import { adjustMetricQuery } from '../utils'; - -describe('ServerlessActions', () => { - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - }); - - describe('fetchFunctions', () => { - it('should successfully fetch functions', () => { - const endpoint = '/functions'; - mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockServerlessFunctions)); - - return testAction( - fetchFunctions, - { functionsPath: endpoint }, - {}, - [], - [ - { type: 'requestFunctionsLoading' }, - { type: 'receiveFunctionsSuccess', payload: mockServerlessFunctions }, - ], - ); - }); - - it('should successfully retry', () => { - const endpoint = '/functions'; - mock - .onGet(endpoint) - .reply(() => new Promise((resolve) => setTimeout(() => resolve(200), Infinity))); - - return testAction( - fetchFunctions, - { functionsPath: endpoint }, - {}, - [], - [{ type: 'requestFunctionsLoading' }], - ); - }); - }); - - describe('fetchMetrics', () => { - it('should return no prometheus', () => { - const endpoint = '/metrics'; - mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); - - return testAction( - fetchMetrics, - { metricsPath: endpoint, hasPrometheus: false }, - {}, - [], - [{ type: 'receiveMetricsNoPrometheus' }], - ); - }); - - it('should successfully fetch metrics', () => { - const endpoint = '/metrics'; - mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockMetrics)); - - return testAction( - fetchMetrics, - { metricsPath: endpoint, hasPrometheus: true }, - {}, - [], - [{ type: 'receiveMetricsSuccess', payload: adjustMetricQuery(mockMetrics) }], - ); - }); - }); -}); diff --git a/spec/frontend/serverless/store/getters_spec.js b/spec/frontend/serverless/store/getters_spec.js deleted file mode 100644 index e1942bd2759..00000000000 --- a/spec/frontend/serverless/store/getters_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import * as getters from '~/serverless/store/getters'; -import serverlessState from '~/serverless/store/state'; -import { mockServerlessFunctions } from '../mock_data'; - -describe('Serverless Store Getters', () => { - let state; - - beforeEach(() => { - state = serverlessState; - }); - - describe('hasPrometheusMissingData', () => { - it('should return false if Prometheus is not installed', () => { - state.hasPrometheus = false; - - expect(getters.hasPrometheusMissingData(state)).toEqual(false); - }); - - it('should return false if Prometheus is installed and there is data', () => { - state.hasPrometheusData = true; - - expect(getters.hasPrometheusMissingData(state)).toEqual(false); - }); - - it('should return true if Prometheus is installed and there is no data', () => { - state.hasPrometheus = true; - state.hasPrometheusData = false; - - expect(getters.hasPrometheusMissingData(state)).toEqual(true); - }); - }); - - describe('getFunctions', () => { - it('should translate the raw function array to group the functions per environment scope', () => { - state.functions = mockServerlessFunctions.functions; - - const funcs = getters.getFunctions(state); - - expect(Object.keys(funcs)).toContain('*'); - expect(funcs['*'].length).toEqual(2); - }); - }); -}); diff --git a/spec/frontend/serverless/store/mutations_spec.js b/spec/frontend/serverless/store/mutations_spec.js deleted file mode 100644 index a1a8f9a2ca7..00000000000 --- a/spec/frontend/serverless/store/mutations_spec.js +++ /dev/null @@ -1,86 +0,0 @@ -import * as types from '~/serverless/store/mutation_types'; -import mutations from '~/serverless/store/mutations'; -import { mockServerlessFunctions, mockMetrics } from '../mock_data'; - -describe('ServerlessMutations', () => { - describe('Functions List Mutations', () => { - it('should ensure loading is true', () => { - const state = {}; - - mutations[types.REQUEST_FUNCTIONS_LOADING](state); - - expect(state.isLoading).toEqual(true); - }); - - it('should set proper state once functions are loaded', () => { - const state = {}; - - mutations[types.RECEIVE_FUNCTIONS_SUCCESS](state, mockServerlessFunctions); - - expect(state.isLoading).toEqual(false); - expect(state.hasFunctionData).toEqual(true); - expect(state.functions).toEqual(mockServerlessFunctions.functions); - }); - - it('should ensure loading has stopped and hasFunctionData is false when there are no functions available', () => { - const state = {}; - - mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state, { knative_installed: true }); - - expect(state.isLoading).toEqual(false); - expect(state.hasFunctionData).toEqual(false); - expect(state.functions).toBe(undefined); - }); - - it('should ensure loading has stopped, and an error is raised', () => { - const state = {}; - - mutations[types.RECEIVE_FUNCTIONS_ERROR](state, 'sample error'); - - expect(state.isLoading).toEqual(false); - expect(state.hasFunctionData).toEqual(false); - expect(state.functions).toBe(undefined); - expect(state.error).not.toBe(undefined); - }); - }); - - describe('Function Details Metrics Mutations', () => { - it('should ensure isLoading and hasPrometheus data flags indicate data is loaded', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_SUCCESS](state, mockMetrics); - - expect(state.isLoading).toEqual(false); - expect(state.hasPrometheusData).toEqual(true); - expect(state.graphData).toEqual(mockMetrics); - }); - - it('should ensure isLoading and hasPrometheus data flags are cleared indicating no functions available', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_NODATA_SUCCESS](state); - - expect(state.isLoading).toEqual(false); - expect(state.hasPrometheusData).toEqual(false); - expect(state.graphData).toBe(undefined); - }); - - it('should properly indicate an error', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_ERROR](state, 'sample error'); - - expect(state.hasPrometheusData).toEqual(false); - expect(state.error).not.toBe(undefined); - }); - - it('should properly indicate when prometheus is installed', () => { - const state = {}; - - mutations[types.RECEIVE_METRICS_NO_PROMETHEUS](state); - - expect(state.hasPrometheus).toEqual(false); - expect(state.hasPrometheusData).toEqual(false); - }); - }); -}); diff --git a/spec/frontend/serverless/utils.js b/spec/frontend/serverless/utils.js deleted file mode 100644 index 7caf7da231e..00000000000 --- a/spec/frontend/serverless/utils.js +++ /dev/null @@ -1,17 +0,0 @@ -export const adjustMetricQuery = (data) => { - const updatedMetric = data.metrics; - - const queries = data.metrics.queries.map((query) => ({ - ...query, - result: query.result.map((result) => ({ - ...result, - values: result.values.map(([timestamp, value]) => ({ - time: new Date(timestamp * 1000).toISOString(), - value: Number(value), - })), - })), - })); - - updatedMetric.queries = queries; - return updatedMetric; -}; diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb index 9a4d7bd996e..e69edbe6dc0 100644 --- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb @@ -274,8 +274,7 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do it 'instantiates a Client' do allow(project).to receive(:import_data).and_return(double(credentials: credentials)) expect(Gitlab::LegacyGithubImport::Client).to receive(:new).with( - credentials[:user], - **{} + credentials[:user] ) subject.client diff --git a/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb b/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb index 81114f5a0b3..2da7d324708 100644 --- a/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb @@ -39,27 +39,17 @@ RSpec.describe Sidebars::Projects::Menus::InfrastructureMenu do subject.renderable_items.delete(find_menu_item(:kubernetes)) end - it 'menu link points to Serverless page' do - expect(subject.link).to eq find_menu_item(:serverless).link + it 'menu link points to Terraform page' do + expect(subject.link).to eq find_menu_item(:terraform).link end - context 'when Serverless menu is not visible' do + context 'when Terraform menu is not visible' do before do - subject.renderable_items.delete(find_menu_item(:serverless)) + subject.renderable_items.delete(find_menu_item(:terraform)) end - it 'menu link points to Terraform page' do - expect(subject.link).to eq find_menu_item(:terraform).link - end - - context 'when Terraform menu is not visible' do - before do - subject.renderable_items.delete(find_menu_item(:terraform)) - end - - it 'menu link points to Google Cloud page' do - expect(subject.link).to eq find_menu_item(:google_cloud).link - end + it 'menu link points to Google Cloud page' do + expect(subject.link).to eq find_menu_item(:google_cloud).link end end end @@ -88,20 +78,6 @@ RSpec.describe Sidebars::Projects::Menus::InfrastructureMenu do it_behaves_like 'access rights checks' end - describe 'Serverless' do - let(:item_id) { :serverless } - - it_behaves_like 'access rights checks' - - context 'when feature :deprecated_serverless is disabled' do - before do - stub_feature_flags(deprecated_serverless: false) - end - - it { is_expected.to be_nil } - end - end - describe 'Terraform' do let(:item_id) { :terraform } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 6066822f4f1..dc0fa6eff66 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -845,6 +845,9 @@ RSpec.describe Project, factory_default: :keep do warn_about_potentially_unwanted_characters warn_about_potentially_unwanted_characters= warn_about_potentially_unwanted_characters? + enforce_auth_checks_on_uploads + enforce_auth_checks_on_uploads= + enforce_auth_checks_on_uploads? ).each do |method| it { is_expected.to delegate_method(method).to(:project_setting).with_arguments(allow_nil: true) } end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5f623eda28a..20d371bdd4b 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -3563,6 +3563,20 @@ RSpec.describe API::Projects do expect(json_response['topics']).to eq(%w[topic2]) end + it 'updates enforce_auth_checks_on_uploads' do + project3.update!(enforce_auth_checks_on_uploads: false) + + project_param = { enforce_auth_checks_on_uploads: true } + + expect { put api("/projects/#{project3.id}", user), params: project_param } + .to change { project3.reload.enforce_auth_checks_on_uploads } + .from(false) + .to(true) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['enforce_auth_checks_on_uploads']).to eq(true) + end + it 'updates squash_option' do project3.update!(squash_option: 'always') diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb index b472819a00b..ef6ff7be840 100644 --- a/spec/support/shared_contexts/navbar_structure_context.rb +++ b/spec/support/shared_contexts/navbar_structure_context.rb @@ -76,7 +76,6 @@ RSpec.shared_context 'project navbar structure' do nav_item: _('Infrastructure'), nav_sub_items: [ _('Kubernetes clusters'), - _('Serverless platform'), _('Terraform') ] }, diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index 22e925e22ae..3943355bffd 100644 --- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -537,24 +537,6 @@ RSpec.describe 'layouts/nav/sidebar/_project' do end describe 'Infrastructure' do - describe 'Serverless platform' do - it 'has a link to the serverless page' do - render - - expect(rendered).to have_link('Serverless platform', href: project_serverless_functions_path(project)) - end - - describe 'when the user does not have access' do - let(:user) { nil } - - it 'does not have a link to the serverless page' do - render - - expect(rendered).not_to have_link('Serverless platform') - end - end - end - describe 'Terraform' do it 'has a link to the terraform page' do render