Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-05-05 06:08:22 +00:00
parent 082a475577
commit 17dffb6c51
105 changed files with 1526 additions and 4575 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -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)

View File

@ -1,3 +0,0 @@
import ServerlessBundle from '~/serverless/serverless_bundle';
new ServerlessBundle(); // eslint-disable-line no-new

View File

@ -1,145 +0,0 @@
<script>
import { GlAreaChart } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import { X_INTERVAL } from '../constants';
import { validateGraphData } from '../utils';
let debouncedResize;
export default {
components: {
GlAreaChart,
},
inheritAttrs: false,
props: {
graphData: {
type: Object,
required: true,
validator: validateGraphData,
},
containerWidth: {
type: Number,
required: true,
},
},
data() {
return {
tooltipPopoverTitle: '',
tooltipPopoverContent: '',
width: this.containerWidth,
};
},
computed: {
chartData() {
return this.graphData.queries.reduce((accumulator, query) => {
accumulator[query.unit] = query.result.reduce((acc, res) => acc.concat(res.values), []);
return accumulator;
}, {});
},
extractTimeData() {
return this.chartData.requests.map((data) => data.time);
},
generateSeries() {
return {
name: __('Invocations'),
type: 'line',
data: this.chartData.requests.map((data) => [data.time, data.value]),
symbolSize: 0,
};
},
getInterval() {
const { result } = this.graphData.queries[0];
if (result.length === 0) {
return 1;
}
const split = result[0].values.reduce(
(acc, pair) => (pair.value > acc ? pair.value : acc),
1,
);
return split < X_INTERVAL ? split : X_INTERVAL;
},
chartOptions() {
return {
xAxis: {
name: 'time',
type: 'time',
axisLabel: {
formatter: (date) => dateFormat(date, 'h:MM TT'),
},
data: this.extractTimeData,
nameTextStyle: {
padding: [18, 0, 0, 0],
},
},
yAxis: {
name: this.yAxisLabel,
nameTextStyle: {
padding: [0, 0, 36, 0],
},
splitNumber: this.getInterval,
},
legend: {
formatter: this.xAxisLabel,
},
series: this.generateSeries,
};
},
xAxisLabel() {
return this.graphData.queries.map((query) => query.label).join(', ');
},
yAxisLabel() {
const [query] = this.graphData.queries;
return `${this.graphData.y_label} (${query.unit})`;
},
},
watch: {
containerWidth: 'onResize',
},
beforeDestroy() {
window.removeEventListener('resize', debouncedResize);
},
created() {
debouncedResize = debounceByAnimationFrame(this.onResize);
window.addEventListener('resize', debouncedResize);
},
methods: {
formatTooltipText(params) {
const [seriesData] = params.seriesData;
this.tooltipPopoverTitle = dateFormat(params.value, 'dd mmm yyyy, h:MMTT');
this.tooltipPopoverContent = `${this.yAxisLabel}: ${seriesData.value[1]}`;
},
onResize() {
const { width } = this.$refs.areaChart.$el.getBoundingClientRect();
this.width = width;
},
},
};
</script>
<template>
<div class="prometheus-graph">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
<div ref="graphWidgets" class="prometheus-graph-widgets">
<slot></slot>
</div>
</div>
<gl-area-chart
ref="areaChart"
v-bind="$attrs"
:data="[] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */"
:option="chartOptions"
:format-tooltip-text="formatTooltipText"
:width="width"
:include-legend-avg-max="false"
>
<template #tooltip-title>{{ tooltipPopoverTitle }}</template>
<template #tooltip-content>{{ tooltipPopoverContent }}</template>
</gl-area-chart>
</div>
</template>

View File

@ -1,39 +0,0 @@
<script>
import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import { mapState } from 'vuex';
import { s__ } from '~/locale';
import { DEPRECATION_POST_LINK } from '../constants';
export default {
components: {
GlEmptyState,
GlLink,
GlSprintf,
},
i18n: {
title: s__('Serverless|Getting started with serverless'),
description: s__(
'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}',
),
},
deprecationPostLink: DEPRECATION_POST_LINK,
computed: {
...mapState(['emptyImagePath', 'helpPath']),
},
};
</script>
<template>
<gl-empty-state :svg-path="emptyImagePath" :title="$options.i18n.title">
<template #description>
<gl-sprintf :message="$options.i18n.description">
<template #postLink="{ content }">
<gl-link :href="$options.deprecationPostLink" target="_blank">{{ content }}</gl-link>
</template>
<template #link="{ content }">
<gl-link :href="helpPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</template>
</gl-empty-state>
</template>

View File

@ -1,65 +0,0 @@
<script>
import ItemCaret from '~/groups/components/item_caret.vue';
import FunctionRow from './function_row.vue';
export default {
components: {
ItemCaret,
FunctionRow,
},
props: {
env: {
type: Array,
required: true,
},
envName: {
type: String,
required: true,
},
},
data() {
return {
isOpen: true,
};
},
computed: {
envId() {
if (this.envName === '*') {
return 'env-global';
}
return `env-${this.envName}`;
},
isOpenClass() {
return {
'is-open': this.isOpen,
};
},
},
methods: {
toggleOpen() {
this.isOpen = !this.isOpen;
},
},
};
</script>
<template>
<li :id="envId" :class="isOpenClass" class="group-row has-children">
<div
class="group-row-contents d-flex justify-content-end align-items-center py-2"
role="button"
@click.stop="toggleOpen"
>
<div class="folder-toggle-wrap d-flex align-items-center">
<item-caret :is-group-open="isOpen" />
</div>
<div class="group-text flex-grow title namespace-title gl-ml-3">
{{ envName }}
</div>
</div>
<ul v-if="isOpen" class="content-list group-list-tree">
<function-row v-for="(f, index) in env" :key="f.name" :index="index" :func="f" />
</ul>
</li>
</template>

View File

@ -1,94 +0,0 @@
<script>
import { isString } from 'lodash';
import { mapState, mapActions, mapGetters } from 'vuex';
import AreaChart from './area.vue';
import MissingPrometheus from './missing_prometheus.vue';
import PodBox from './pod_box.vue';
import Url from './url.vue';
export default {
components: {
PodBox,
Url,
AreaChart,
MissingPrometheus,
},
props: {
func: {
type: Object,
required: true,
},
hasPrometheus: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
elWidth: 0,
};
},
computed: {
name() {
return this.func.name;
},
description() {
return isString(this.func.description) ? this.func.description : '';
},
funcUrl() {
return this.func.url;
},
podCount() {
return Number(this.func.podcount) || 0;
},
...mapState(['graphData', 'hasPrometheusData']),
...mapGetters(['hasPrometheusMissingData']),
},
created() {
this.fetchMetrics({
metricsPath: this.func.metricsUrl,
hasPrometheus: this.hasPrometheus,
});
},
mounted() {
this.elWidth = this.$el.clientWidth;
},
methods: {
...mapActions(['fetchMetrics']),
},
};
</script>
<template>
<section id="serverless-function-details">
<h3 class="serverless-function-name">{{ name }}</h3>
<div class="gl-mb-3 serverless-function-description">
<div v-for="(line, index) in description.split('\n')" :key="index">{{ line }}</div>
</div>
<url :uri="funcUrl" />
<h4>{{ s__('ServerlessDetails|Kubernetes Pods') }}</h4>
<div v-if="podCount > 0">
<p>
<b v-if="podCount == 1">{{ podCount }} {{ s__('ServerlessDetails|pod in use') }}</b>
<b v-else>{{ podCount }} {{ s__('ServerlessDetails|pods in use') }}</b>
</p>
<pod-box :count="podCount" />
<p>
{{
s__('ServerlessDetails|Number of Kubernetes pods in use over time based on necessity.')
}}
</p>
</div>
<div v-else>
<p>{{ s__('ServerlessDetails|No pods loaded at this time.') }}</p>
</div>
<area-chart v-if="hasPrometheusData" :graph-data="graphData" :container-width="elWidth" />
<missing-prometheus
v-if="!hasPrometheus || hasPrometheusMissingData"
:missing-data="hasPrometheusMissingData"
/>
</section>
</template>

View File

@ -1,77 +0,0 @@
<script>
import { isString } from 'lodash';
import { visitUrl } from '~/lib/utils/url_utility';
import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
import Url from './url.vue';
export default {
components: {
Timeago,
Url,
},
props: {
func: {
type: Object,
required: true,
},
},
computed: {
name() {
return this.func.name;
},
description() {
if (!isString(this.func.description)) {
return '';
}
const desc = this.func.description.split('\n');
if (desc.length > 1) {
return desc[1];
}
return desc[0];
},
detailUrl() {
return this.func.detail_url;
},
targetUrl() {
return this.func.url;
},
image() {
return this.func.image;
},
timestamp() {
return this.func.created_at;
},
},
methods: {
checkClass(element) {
if (element.closest('.no-expand') === null) {
return true;
}
return false;
},
openDetails(e) {
if (this.checkClass(e.target)) {
visitUrl(this.detailUrl);
}
},
},
};
</script>
<template>
<li :id="name" class="group-row">
<div class="group-row-contents py-2" role="button" @click="openDetails">
<p class="float-right text-right">
<span>{{ image }}</span
><br />
<timeago :time="timestamp" />
</p>
<b>{{ name }}</b>
<div v-for="line in description.split('\n')" :key="line">{{ line }}</div>
<url :uri="targetUrl" class="gl-mt-3 no-expand" />
</div>
</li>
</template>

View File

@ -1,139 +0,0 @@
<script>
import {
GlLink,
GlAlert,
GlSprintf,
GlLoadingIcon,
GlSafeHtmlDirective as SafeHtml,
} from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import { sprintf, s__ } from '~/locale';
import { CHECKING_INSTALLED, DEPRECATION_POST_LINK } from '../constants';
import EmptyState from './empty_state.vue';
import EnvironmentRow from './environment_row.vue';
export default {
components: {
EnvironmentRow,
EmptyState,
GlLink,
GlAlert,
GlSprintf,
GlLoadingIcon,
},
directives: {
SafeHtml,
},
deprecationPostLink: DEPRECATION_POST_LINK,
computed: {
...mapState(['installed', 'isLoading', 'hasFunctionData', 'helpPath', 'statusPath']),
...mapGetters(['getFunctions']),
checkingInstalled() {
return this.installed === CHECKING_INSTALLED;
},
isInstalled() {
return this.installed === true;
},
noServerlessConfigFile() {
return sprintf(
s__(
'Serverless|Your repository does not have a corresponding %{startTag}serverless.yml%{endTag} file.',
),
{ startTag: '<code>', endTag: '</code>' },
false,
);
},
noGitlabYamlConfigured() {
return sprintf(
s__('Serverless|Your %{startTag}.gitlab-ci.yml%{endTag} file is not properly configured.'),
{ startTag: '<code>', endTag: '</code>' },
false,
);
},
mismatchedServerlessFunctions() {
return sprintf(
s__(
"Serverless|The functions listed in the %{startTag}serverless.yml%{endTag} file don't match the namespace of your cluster.",
),
{ startTag: '<code>', endTag: '</code>' },
false,
);
},
},
created() {
this.fetchFunctions({
functionsPath: this.statusPath,
});
},
methods: {
...mapActions(['fetchFunctions']),
},
};
</script>
<template>
<section id="serverless-functions" class="flex-grow">
<gl-alert class="gl-mt-6" variant="warning" :dismissible="false">
<gl-sprintf
:message="s__('Serverless|Serverless was %{linkStart}deprecated%{linkEnd} in GitLab 14.3.')"
><template #link="{ content }"
><gl-link :href="$options.deprecationPostLink" target="_blank">{{
content
}}</gl-link></template
></gl-sprintf
>
</gl-alert>
<gl-loading-icon v-if="checkingInstalled" size="lg" class="gl-mt-3 gl-mb-3" />
<div v-else-if="isInstalled">
<div v-if="hasFunctionData">
<div class="groups-list-tree-container js-functions-wrapper">
<ul class="content-list group-list-tree">
<environment-row
v-for="(env, index) in getFunctions"
:key="index"
:env="env"
:env-name="index"
/>
</ul>
</div>
<gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-3 gl-mb-3 js-functions-loader" />
</div>
<div v-else class="empty-state js-empty-state">
<div class="text-content">
<h4 class="state-title text-center">{{ s__('Serverless|No functions available') }}</h4>
<p class="state-description">
{{
s__(
'Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:',
)
}}
</p>
<ul>
<li v-safe-html="noServerlessConfigFile"></li>
<li v-safe-html="noGitlabYamlConfigured"></li>
<li v-safe-html="mismatchedServerlessFunctions"></li>
<li>{{ s__('Serverless|The deploy job has not finished.') }}</li>
</ul>
<p>
{{
s__(
'Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available.',
)
}}
</p>
<div class="text-center">
<gl-link :href="helpPath" class="btn btn-success">{{
s__('Serverless|Learn more about Serverless')
}}</gl-link>
</div>
</div>
</div>
</div>
<empty-state v-else />
</section>
</template>

View File

@ -1,57 +0,0 @@
<script>
import { GlButton, GlLink } from '@gitlab/ui';
import { mapState } from 'vuex';
import { s__ } from '~/locale';
export default {
components: {
GlButton,
GlLink,
},
props: {
missingData: {
type: Boolean,
required: true,
},
},
computed: {
...mapState(['clustersPath', 'helpPath']),
missingStateClass() {
return this.missingData ? 'missing-prometheus-state' : 'empty-prometheus-state';
},
prometheusHelpPath() {
return `${this.helpPath}#prometheus-support`;
},
description() {
return this.missingData
? s__(`ServerlessDetails|Invocation metrics loading or not available at this time.`)
: s__(
`ServerlessDetails|Function invocation metrics require the Prometheus cluster integration.`,
);
},
},
};
</script>
<template>
<div class="row" :class="missingStateClass">
<div class="col-12">
<div class="text-content">
<h4 class="state-title text-left">{{ s__(`ServerlessDetails|Invocations`) }}</h4>
<p class="state-description">
{{ description }}
<gl-link :href="prometheusHelpPath">{{
s__(`ServerlessDetails|More information`)
}}</gl-link
>.
</p>
<div v-if="!missingData" class="text-left">
<gl-button :href="clustersPath" variant="success" category="primary">
{{ s__('ServerlessDetails|Configure cluster.') }}
</gl-button>
</div>
</div>
</div>
</div>
</template>

View File

@ -1,36 +0,0 @@
<script>
export default {
props: {
count: {
type: Number,
required: true,
},
color: {
type: String,
required: false,
default: 'green',
},
},
methods: {
boxOffset(i) {
return 20 * (i - 1);
},
},
};
</script>
<template>
<svg :width="boxOffset(count + 1)" :height="20">
<rect
v-for="i in count"
:key="i"
width="15"
height="15"
rx="5"
ry="5"
:fill="color"
:x="boxOffset(i)"
y="0"
/>
</svg>
</template>

View File

@ -1,28 +0,0 @@
<script>
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default {
components: {
ClipboardButton,
},
props: {
uri: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="clipboard-group">
<div class="gl-cursor-text label label-monospace monospace" data-testid="url-text-field">
{{ uri }}
</div>
<clipboard-button
:text="uri"
:title="s__('ServerlessURL|Copy URL')"
class="input-group-text js-clipboard-btn"
/>
</div>
</template>

View File

@ -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';

View File

@ -1,3 +0,0 @@
import createEventHub from '~/helpers/event_hub_factory';
export default createEventHub();

View File

@ -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();
}
}

View File

@ -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,
});
});
};

View File

@ -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);

View File

@ -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;

View File

@ -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';

View File

@ -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;
},
};

View File

@ -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,
});

View File

@ -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]),
}),
{},
);

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -23,6 +23,10 @@ class Projects::UploadsController < Projects::ApplicationController
FileUploader
end
def target_project
model
end
def find_model
return @project if @project

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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?,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
7418b98f33ada13dedab493ad8a969808a18db2fa0188e428b1c685aabb3bc66

View File

@ -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)),

View File

@ -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).

View File

@ -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|

View File

@ -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)**

View File

@ -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,

View File

@ -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.

View File

@ -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)**

View File

@ -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

View File

@ -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

View File

@ -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)**

View File

@ -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

View File

@ -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

View File

@ -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.). |

View File

@ -2666,7 +2666,7 @@ Input type: `ExternalAuditEventDestinationUpdateInput`
| ---- | ---- | ----------- |
| <a id="mutationexternalauditeventdestinationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationexternalauditeventdestinationupdatedestinationurl"></a>`destinationUrl` | [`String`](#string) | Destination URL to change. |
| <a id="mutationexternalauditeventdestinationupdateid"></a>`id` | [`AuditEventsExternalAuditEventDestinationID!`](#auditeventsexternalauditeventdestinationid) | ID of external audit event destination to destroy. |
| <a id="mutationexternalauditeventdestinationupdateid"></a>`id` | [`AuditEventsExternalAuditEventDestinationID!`](#auditeventsexternalauditeventdestinationid) | ID of external audit event destination to update. |
#### Fields

View File

@ -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:
- <https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations-removals-and-breaking-changes>
- <https://about.gitlab.com/handbook/marketing/blog/release-posts/#update-the-deprecations-and-removals-docs>

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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 `<S3_bucket_name>` 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 <S3_bucket_name>
- sam deploy --template-file packaged.yaml --stack-name gitlabpoc --s3-bucket <S3_bucket_name> --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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -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/<template_name>`. 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 <your_function_name>
```
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 <letsencrypt-client@eff.org>" [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 '*.<namespace>.example.com'
```
Where `<namespace>` is the namespace created by GitLab for your serverless project (composed of `<project_name>-<project_id>-<environment>`) 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:<version>`.
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.

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 ""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,22 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EmptyStateComponent should render content 1`] = `
"<section class=\\"gl-display-flex empty-state gl-text-center gl-flex-direction-column\\">
<div class=\\"gl-max-w-full\\">
<div class=\\"svg-250 svg-content\\"><img src=\\"/image.svg\\" alt=\\"\\" role=\\"img\\" class=\\"gl-max-w-full gl-dark-invert-keep-hue\\"></div>
</div>
<div class=\\"gl-max-w-full gl-m-auto\\">
<div class=\\"gl-mx-auto gl-my-0 gl-p-5\\">
<h1 class=\\"gl-font-size-h-display gl-line-height-36 h4\\">
Getting started with serverless
</h1>
<p class=\\"gl-mt-3\\">Serverless was <gl-link-stub target=\\"_blank\\" href=\\"https://about.gitlab.com/releases/2021/09/22/gitlab-14-3-released/#gitlab-serverless\\">deprecated</gl-link-stub>. But if you opt to use it, you must install Knative in your Kubernetes cluster first. <gl-link-stub href=\\"/help\\">Learn more.</gl-link-stub>
</p>
<div class=\\"gl-display-flex gl-flex-wrap gl-justify-content-center\\">
<!---->
<!---->
</div>
</div>
</div>
</section>"
`;

View File

@ -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)');
});
});
});
});

View File

@ -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();
});
});

View File

@ -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');
});
});
});

View File

@ -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);
});
});
});

View File

@ -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
});
});

View File

@ -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);
});
});

View File

@ -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.',
);
});
});

View File

@ -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();
});
});

View File

@ -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();
});
});

View File

@ -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,
},
],
},
],
},
],
};

View File

@ -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) }],
);
});
});
});

View File

@ -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);
});
});
});

View File

@ -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);
});
});
});

View File

@ -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;
};

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More