From 7021455bd1ed7b125c55eb1b33c5a01f2bc55ee0 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 17 Nov 2022 11:33:21 +0000 Subject: Add latest changes from gitlab-org/gitlab@15-6-stable-ee --- lib/api/access_requests.rb | 39 ++- lib/api/admin/ci/variables.rb | 49 ++-- lib/api/admin/instance_clusters.rb | 52 +++- lib/api/admin/plan_limits.rb | 28 +- lib/api/alert_management_alerts.rb | 2 +- lib/api/api.rb | 185 ++++++------- lib/api/appearance.rb | 1 + lib/api/applications.rb | 21 +- lib/api/badges.rb | 9 +- lib/api/boards.rb | 2 +- lib/api/branches.rb | 45 +++- lib/api/bulk_imports.rb | 48 +++- lib/api/ci/helpers/runner.rb | 30 +-- lib/api/ci/job_artifacts.rb | 6 +- lib/api/ci/jobs.rb | 94 +++++-- lib/api/ci/pipeline_schedules.rb | 144 +++++++--- lib/api/ci/pipelines.rb | 151 ++++++++--- lib/api/ci/resource_groups.rb | 54 +++- lib/api/ci/runner.rb | 75 +++--- lib/api/ci/runners.rb | 292 ++++++++++++--------- lib/api/ci/secure_files.rb | 4 +- lib/api/ci/triggers.rb | 66 ++++- lib/api/ci/variables.rb | 39 ++- lib/api/clusters/agent_tokens.rb | 20 +- lib/api/clusters/agents.rb | 22 +- lib/api/commit_statuses.rb | 55 ++-- lib/api/commits.rb | 204 +++++++++++--- lib/api/concerns/packages/conan_endpoints.rb | 6 +- lib/api/container_registry_event.rb | 14 +- lib/api/container_repositories.rb | 2 +- lib/api/debian_project_packages.rb | 4 +- lib/api/dependency_proxy.rb | 13 +- lib/api/deploy_keys.rb | 75 +++++- lib/api/deploy_tokens.rb | 100 +++++-- lib/api/deployments.rb | 113 ++++++-- lib/api/discussions.rb | 95 +++---- lib/api/entities/application.rb | 10 +- lib/api/entities/application_statistics.rb | 32 ++- lib/api/entities/application_with_secret.rb | 3 +- lib/api/entities/basic_project_details.rb | 23 +- lib/api/entities/basic_ref.rb | 3 +- lib/api/entities/basic_release_details.rb | 12 +- lib/api/entities/basic_repository_storage_move.rb | 10 +- lib/api/entities/basic_snippet.rb | 26 +- lib/api/entities/branch.rb | 44 +++- lib/api/entities/bulk_import.rb | 12 +- lib/api/entities/bulk_imports/entity.rb | 28 +- lib/api/entities/bulk_imports/entity_failure.rb | 18 +- lib/api/entities/bulk_imports/export_status.rb | 8 +- lib/api/entities/ci/job.rb | 13 +- lib/api/entities/ci/job_artifact.rb | 7 +- lib/api/entities/ci/job_artifact_file.rb | 4 +- lib/api/entities/ci/job_basic.rb | 27 +- lib/api/entities/ci/lint/result.rb | 17 +- lib/api/entities/ci/pipeline.rb | 18 +- lib/api/entities/ci/pipeline_basic.rb | 17 +- lib/api/entities/ci/pipeline_schedule.rb | 12 +- lib/api/entities/ci/resource_group.rb | 6 +- lib/api/entities/ci/runner.rb | 22 +- lib/api/entities/ci/secure_file.rb | 2 + lib/api/entities/ci/variable.rb | 14 +- lib/api/entities/commit.rb | 27 +- lib/api/entities/commit_detail.rb | 6 +- lib/api/entities/commit_note.rb | 20 +- lib/api/entities/commit_signature.rb | 4 +- lib/api/entities/commit_stats.rb | 4 +- lib/api/entities/commit_status.rb | 18 +- lib/api/entities/compare.rb | 13 +- lib/api/entities/container_registry.rb | 14 +- lib/api/entities/contributor.rb | 6 +- lib/api/entities/custom_attribute.rb | 4 +- lib/api/entities/deploy_key.rb | 12 +- lib/api/entities/deploy_keys_project.rb | 2 +- lib/api/entities/deploy_token.rb | 9 +- lib/api/entities/deploy_token_with_token.rb | 2 +- lib/api/entities/deployment.rb | 9 +- lib/api/entities/diff.rb | 16 +- lib/api/entities/entity_helpers.rb | 4 +- lib/api/entities/environment.rb | 4 +- lib/api/entities/environment_basic.rb | 7 +- lib/api/entities/error_tracking.rb | 18 +- lib/api/entities/feature.rb | 4 +- lib/api/entities/feature_flag.rb | 12 +- lib/api/entities/feature_flag/scope.rb | 4 +- lib/api/entities/feature_flag/strategy.rb | 6 +- lib/api/entities/feature_flag/user_list.rb | 14 +- lib/api/entities/feature_gate.rb | 4 +- lib/api/entities/freeze_period.rb | 8 +- lib/api/entities/go_module_version.rb | 4 +- lib/api/entities/hook.rb | 16 +- lib/api/entities/issuable_entity.rb | 14 +- lib/api/entities/issue_basic.rb | 16 +- lib/api/entities/license.rb | 25 +- lib/api/entities/license_basic.rb | 6 +- lib/api/entities/markdown.rb | 9 + lib/api/entities/merge_request_approvals.rb | 6 +- lib/api/entities/merge_request_basic.rb | 7 + lib/api/entities/merge_request_simple.rb | 7 +- lib/api/entities/metadata.rb | 9 +- lib/api/entities/metrics/dashboard/annotation.rb | 14 +- lib/api/entities/metrics/user_starred_dashboard.rb | 5 +- lib/api/entities/ml/mlflow/run.rb | 2 +- lib/api/entities/ml/mlflow/run_info.rb | 2 +- lib/api/entities/ml/mlflow/update_run.rb | 8 +- lib/api/entities/package.rb | 29 +- lib/api/entities/package_file.rb | 11 +- lib/api/entities/personal_access_token.rb | 13 +- lib/api/entities/plan_limit.rb | 34 +-- lib/api/entities/project.rb | 159 +++++------ lib/api/entities/project_daily_fetches.rb | 4 +- lib/api/entities/project_daily_statistics.rb | 4 +- lib/api/entities/project_export_status.rb | 14 +- lib/api/entities/project_group_link.rb | 6 +- lib/api/entities/project_hook.rb | 15 +- lib/api/entities/project_identity.rb | 11 +- lib/api/entities/project_import_failed_relation.rb | 11 +- lib/api/entities/project_import_status.rb | 16 +- lib/api/entities/project_integration.rb | 2 +- lib/api/entities/project_integration_basic.rb | 25 +- lib/api/entities/project_repository_storage.rb | 10 +- lib/api/entities/project_with_access.rb | 36 ++- lib/api/entities/protected_branch.rb | 10 +- lib/api/entities/protected_ref_access.rb | 10 +- lib/api/entities/protected_tag.rb | 2 +- lib/api/entities/pull_mirror.rb | 19 ++ lib/api/entities/release.rb | 18 +- lib/api/entities/releases/evidence.rb | 6 +- lib/api/entities/releases/link.rb | 20 +- lib/api/entities/releases/source.rb | 4 +- lib/api/entities/remote_mirror.rb | 20 +- lib/api/entities/resource_access_token.rb | 7 +- lib/api/entities/resource_milestone_event.rb | 12 +- lib/api/entities/snippet.rb | 8 +- .../entities/snippets/repository_storage_move.rb | 2 +- lib/api/entities/ssh_key.rb | 11 +- lib/api/entities/tag.rb | 6 +- lib/api/entities/tag_release.rb | 4 +- lib/api/entities/templates_list.rb | 4 +- lib/api/entities/tree_object.rb | 7 +- lib/api/entities/trigger.rb | 10 +- lib/api/entities/user_agent_detail.rb | 6 +- lib/api/entities/user_associations_count.rb | 23 ++ lib/api/entities/user_basic.rb | 19 +- lib/api/entities/user_counts.rb | 25 ++ lib/api/entities/user_public.rb | 24 +- lib/api/entities/user_safe.rb | 5 +- lib/api/entities/wiki_attachment.rb | 14 +- lib/api/entities/wiki_page.rb | 6 +- lib/api/entities/wiki_page_basic.rb | 6 +- lib/api/entities/x509_certificate.rb | 17 +- lib/api/entities/x509_issuer.rb | 11 +- lib/api/entities/x509_signature.rb | 2 +- lib/api/environments.rb | 102 +++++-- lib/api/error_tracking/client_keys.rb | 23 +- lib/api/error_tracking/collector.rb | 4 +- lib/api/error_tracking/project_settings.rb | 30 ++- lib/api/feature_flags.rb | 77 ++++-- lib/api/feature_flags_user_lists.rb | 56 +++- lib/api/features.rb | 45 +++- lib/api/files.rb | 77 ++++-- lib/api/freeze_periods.rb | 64 +++-- lib/api/generic_packages.rb | 4 +- lib/api/geo.rb | 7 + lib/api/go_proxy.rb | 52 ++-- lib/api/group_avatar.rb | 4 +- lib/api/group_clusters.rb | 42 ++- lib/api/group_container_repositories.rb | 13 +- lib/api/group_export.rb | 45 ++++ lib/api/group_import.rb | 11 +- lib/api/group_packages.rb | 19 +- lib/api/group_variables.rb | 38 ++- lib/api/helpers.rb | 13 +- lib/api/helpers/internal_helpers.rb | 7 +- lib/api/helpers/label_helpers.rb | 5 +- lib/api/helpers/merge_requests_helpers.rb | 5 + .../helpers/packages/dependency_proxy_helpers.rb | 2 +- lib/api/helpers/packages/npm.rb | 2 +- lib/api/helpers/packages_helpers.rb | 30 +++ lib/api/helpers/projects_helpers.rb | 4 +- lib/api/helpers/users_helpers.rb | 7 + lib/api/helpers/web_hooks_helpers.rb | 6 +- lib/api/import_bitbucket_server.rb | 8 + lib/api/import_github.rb | 24 +- lib/api/integrations.rb | 51 +++- lib/api/internal/kubernetes.rb | 28 +- lib/api/internal/pages.rb | 1 + lib/api/invitations.rb | 20 +- lib/api/issue_links.rb | 55 +++- lib/api/issues.rb | 2 +- lib/api/keys.rb | 12 +- lib/api/labels.rb | 2 +- lib/api/lint.rb | 43 ++- lib/api/markdown.rb | 16 +- lib/api/maven_packages.rb | 14 +- lib/api/merge_request_approvals.rb | 28 +- lib/api/merge_request_diffs.rb | 11 +- lib/api/merge_requests.rb | 2 +- lib/api/metadata.rb | 57 +--- lib/api/metrics/dashboard/annotations.rb | 25 +- lib/api/metrics/user_starred_dashboards.rb | 22 +- lib/api/ml/mlflow.rb | 24 +- lib/api/npm_project_packages.rb | 2 +- lib/api/nuget_project_packages.rb | 4 +- lib/api/package_files.rb | 22 +- lib/api/pages.rb | 2 +- lib/api/pages_domains.rb | 2 +- lib/api/pagination_params.rb | 5 +- lib/api/personal_access_tokens.rb | 60 +++-- lib/api/personal_access_tokens/self_information.rb | 18 ++ lib/api/project_clusters.rb | 49 +++- lib/api/project_container_repositories.rb | 8 +- lib/api/project_debian_distributions.rb | 2 +- lib/api/project_events.rb | 9 +- lib/api/project_export.rb | 61 ++++- lib/api/project_hooks.rb | 45 +++- lib/api/project_import.rb | 48 +++- lib/api/project_milestones.rb | 2 +- lib/api/project_packages.rb | 2 +- lib/api/project_repository_storage_moves.rb | 17 +- lib/api/project_snapshots.rb | 5 + lib/api/project_snippets.rb | 52 +++- lib/api/project_statistics.rb | 12 +- lib/api/project_templates.rb | 27 +- lib/api/projects.rb | 7 +- lib/api/projects_relation_builder.rb | 4 + lib/api/protected_branches.rb | 72 ++++- lib/api/protected_tags.rb | 46 +++- lib/api/pypi_packages.rb | 33 ++- lib/api/release/links.rb | 75 ++++-- lib/api/releases.rb | 197 ++++++++++---- lib/api/remote_mirrors.rb | 58 +++- lib/api/repositories.rb | 76 ++++-- lib/api/resource_access_tokens.rb | 35 ++- lib/api/resource_milestone_events.rb | 17 +- lib/api/rpm_project_packages.rb | 27 +- lib/api/rubygem_packages.rb | 2 +- lib/api/search.rb | 8 +- lib/api/settings.rb | 1 + lib/api/snippet_repository_storage_moves.rb | 15 +- lib/api/snippets.rb | 46 +++- lib/api/statistics.rb | 2 +- lib/api/submodules.rb | 31 ++- lib/api/suggestions.rb | 6 +- lib/api/system_hooks.rb | 54 +++- lib/api/tags.rb | 45 +++- lib/api/terraform/modules/v1/packages.rb | 72 ++++- lib/api/terraform/state.rb | 76 +++++- lib/api/terraform/state_version.rb | 22 +- lib/api/topics.rb | 10 +- lib/api/unleash.rb | 34 ++- lib/api/user_counts.rb | 9 +- lib/api/users.rb | 57 +++- lib/api/v3/github.rb | 2 +- .../validations/validators/email_or_email_list.rb | 7 +- lib/api/wikis.rb | 34 ++- 255 files changed, 4699 insertions(+), 1860 deletions(-) create mode 100644 lib/api/entities/markdown.rb create mode 100644 lib/api/entities/pull_mirror.rb create mode 100644 lib/api/entities/user_associations_count.rb create mode 100644 lib/api/entities/user_counts.rb (limited to 'lib/api') diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index 74f6515f07f..38a9856ca58 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -19,6 +19,7 @@ module API desc "Gets a list of access requests for a #{source_type}." do detail 'This feature was introduced in GitLab 8.11.' success Entities::AccessRequester + tags %w[access_requests] end params do use :pagination @@ -37,6 +38,24 @@ module API desc "Requests access for the authenticated user to a #{source_type}." do detail 'This feature was introduced in GitLab 8.11.' success Entities::AccessRequester + success [ + { + code: 200, + model: Entities::AccessRequester, + message: 'successful operation', + examples: { + successfull_response: { + "id" => 1, + "username" => "raymond_smith", + "name" => "Raymond Smith", + "state" => "active", + "created_at" => "2012-10-22T14:13:35Z", + "access_level" => 20 + } + } + } + ] + tags %w[access_requests] end post ":id/access_requests" do source = find_source(source_type, params[:id]) @@ -51,7 +70,24 @@ module API desc 'Approves an access request for the given user.' do detail 'This feature was introduced in GitLab 8.11.' - success Entities::Member + success [ + { + code: 200, + model: Entities::AccessRequester, + message: 'successful operation', + examples: { + successfull_response: { + "id" => 1, + "username" => "raymond_smith", + "name" => "Raymond Smith", + "state" => "active", + "created_at" => "2012-10-22T14:13:35Z", + "access_level" => 20 + } + } + } + ] + tags %w[access_requests] end params do requires :user_id, type: Integer, desc: 'The user ID of the access requester' @@ -74,6 +110,7 @@ module API desc 'Denies an access request for the given user.' do detail 'This feature was introduced in GitLab 8.11.' + tags %w[access_requests] end params do requires :user_id, type: Integer, desc: 'The user ID of the access requester' diff --git a/lib/api/admin/ci/variables.rb b/lib/api/admin/ci/variables.rb index 0462878c90c..bc351e27f99 100644 --- a/lib/api/admin/ci/variables.rb +++ b/lib/api/admin/ci/variables.rb @@ -13,8 +13,9 @@ module API namespace 'admin' do namespace 'ci' do namespace 'variables' do - desc 'Get instance-level variables' do + desc 'List all instance-level variables' do success Entities::Ci::Variable + tags %w[ci_variables] end params do use :pagination @@ -25,11 +26,13 @@ module API present paginate(variables), with: Entities::Ci::Variable end - desc 'Get a specific variable from a group' do + desc 'Get the details of a specific instance-level variable' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Instance Variable Not Found' }] + tags %w[ci_variables] end params do - requires :key, type: String, desc: 'The key of the variable' + requires :key, type: String, desc: 'The key of a variable' end get ':key' do key = params[:key] @@ -42,28 +45,35 @@ module API desc 'Create a new instance-level variable' do success Entities::Ci::Variable + failure [{ code: 400, message: '400 Bad Request' }] + tags %w[ci_variables] end + route_setting :log_safety, { safe: %w[key], unsafe: %w[value] } params do requires :key, type: String, - desc: 'The key of the variable' + desc: 'The key of the variable. Max 255 characters' requires :value, type: String, - desc: 'The value of the variable' + desc: 'The value of a variable' optional :protected, - type: String, + type: Boolean, desc: 'Whether the variable is protected' optional :masked, - type: String, + type: Boolean, desc: 'Whether the variable is masked' + optional :raw, + type: Boolean, + desc: 'Whether the variable will be expanded' + optional :variable_type, type: String, values: ::Ci::InstanceVariable.variable_types.keys, - desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' + desc: 'The type of a variable. Available types are: env_var (default) and file' end post '/' do variable_params = declared_params(include_missing: false) @@ -77,30 +87,37 @@ module API end end - desc 'Update an existing instance-variable' do + desc 'Update an instance-level variable' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Instance Variable Not Found' }] + tags %w[ci_variables] end + route_setting :log_safety, { safe: %w[key], unsafe: %w[value] } params do optional :key, type: String, - desc: 'The key of the variable' + desc: 'The key of a variable' optional :value, type: String, - desc: 'The value of the variable' + desc: 'The value of a variable' optional :protected, - type: String, + type: Boolean, desc: 'Whether the variable is protected' optional :masked, - type: String, + type: Boolean, desc: 'Whether the variable is masked' + optional :raw, + type: Boolean, + desc: 'Whether the variable will be expanded' + optional :variable_type, type: String, values: ::Ci::InstanceVariable.variable_types.keys, - desc: 'The type of variable, must be one of env_var or file' + desc: 'The type of a variable. Available types are: env_var (default) and file' end put ':key' do variable = ::Ci::InstanceVariable.find_by_key(params[:key]) @@ -118,9 +135,11 @@ module API desc 'Delete an existing instance-level variable' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Instance Variable Not Found' }] + tags %w[ci_variables] end params do - requires :key, type: String, desc: 'The key of the variable' + requires :key, type: String, desc: 'The key of a variable' end delete ':key' do variable = ::Ci::InstanceVariable.find_by_key(params[:key]) diff --git a/lib/api/admin/instance_clusters.rb b/lib/api/admin/instance_clusters.rb index 7163225777a..f848103d9a0 100644 --- a/lib/api/admin/instance_clusters.rb +++ b/lib/api/admin/instance_clusters.rb @@ -14,16 +14,28 @@ module API end namespace 'admin' do - desc "Get list of all instance clusters" do - detail "This feature was introduced in GitLab 13.2." + desc 'List instance clusters' do + detail 'This feature was introduced in GitLab 13.2. Returns a list of instance clusters.' + success Entities::Cluster + failure [ + { code: 403, message: 'Forbidden' } + ] + is_array true + tags %w[clusters] end get '/clusters' do authorize! :read_cluster, clusterable_instance present paginate(clusters_for_current_user), with: Entities::Cluster end - desc "Get a single instance cluster" do - detail "This feature was introduced in GitLab 13.2." + desc 'Get a single instance cluster' do + detail 'This feature was introduced in GitLab 13.2. Returns a single instance cluster.' + success Entities::Cluster + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :cluster_id, type: Integer, desc: "The cluster ID" @@ -34,8 +46,15 @@ module API present cluster, with: Entities::Cluster end - desc "Add an instance cluster" do - detail "This feature was introduced in GitLab 13.2." + desc 'Add existing instance cluster' do + detail 'This feature was introduced in GitLab 13.2. Adds an existing Kubernetes instance cluster.' + success Entities::Cluster + failure [ + { code: 400, message: 'Validation error' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :name, type: String, desc: 'Cluster name' @@ -67,8 +86,15 @@ module API end end - desc "Update an instance cluster" do - detail "This feature was introduced in GitLab 13.2." + desc 'Edit instance cluster' do + detail 'This feature was introduced in GitLab 13.2. Updates an existing instance cluster.' + success Entities::Cluster + failure [ + { code: 400, message: 'Validation error' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :cluster_id, type: Integer, desc: 'The cluster ID' @@ -98,8 +124,14 @@ module API end end - desc "Remove a cluster" do - detail "This feature was introduced in GitLab 13.2." + desc 'Delete instance cluster' do + detail 'This feature was introduced in GitLab 13.2. Deletes an existing instance cluster. Does not remove existing resources within the connected Kubernetes cluster.' + success Entities::Cluster + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :cluster_id, type: Integer, desc: "The cluster ID" diff --git a/lib/api/admin/plan_limits.rb b/lib/api/admin/plan_limits.rb index 7ce70d85d46..49b41b44a18 100644 --- a/lib/api/admin/plan_limits.rb +++ b/lib/api/admin/plan_limits.rb @@ -5,6 +5,8 @@ module API class PlanLimits < ::API::Base before { authenticated_as_admin! } + PLAN_LIMITS_TAGS = %w[plan_limits].freeze + feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned helpers do @@ -17,10 +19,17 @@ module API end desc 'Get current plan limits' do + detail 'List the current limits of a plan on the GitLab instance.' success Entities::PlanLimit + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' } + ] + tags PLAN_LIMITS_TAGS end params do - optional :plan_name, type: String, values: Plan.all_plans, default: Plan::DEFAULT, desc: 'Name of the plan' + optional :plan_name, type: String, values: Plan.all_plans, default: Plan::DEFAULT, + desc: 'Name of the plan to get the limits from. Default: default.' end get "application/plan_limits" do params = declared_params(include_missing: false) @@ -29,16 +38,24 @@ module API present plan.actual_limits, with: Entities::PlanLimit end - desc 'Modify plan limits' do + desc 'Change plan limits' do + detail 'Modify the limits of a plan on the GitLab instance.' success Entities::PlanLimit + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' } + ] + tags PLAN_LIMITS_TAGS end params do - requires :plan_name, type: String, values: Plan.all_plans, desc: 'Name of the plan' + requires :plan_name, type: String, values: Plan.all_plans, desc: 'Name of the plan to update' optional :ci_pipeline_size, type: Integer, desc: 'Maximum number of jobs in a single pipeline' optional :ci_active_jobs, type: Integer, desc: 'Total number of jobs in currently active pipelines' optional :ci_active_pipelines, type: Integer, desc: 'Maximum number of active pipelines per project' - optional :ci_project_subscriptions, type: Integer, desc: 'Maximum number of pipeline subscriptions to and from a project' + optional :ci_project_subscriptions, type: Integer, + desc: 'Maximum number of pipeline subscriptions to and from a project' optional :ci_pipeline_schedules, type: Integer, desc: 'Maximum number of pipeline schedules' optional :ci_needs_size_limit, type: Integer, desc: 'Maximum number of DAG dependencies that a job can have' optional :ci_registered_group_runners, type: Integer, desc: 'Maximum number of runners registered per group' @@ -50,7 +67,8 @@ module API optional :npm_max_file_size, type: Integer, desc: 'Maximum NPM package file size in bytes' optional :nuget_max_file_size, type: Integer, desc: 'Maximum NuGet package file size in bytes' optional :pypi_max_file_size, type: Integer, desc: 'Maximum PyPI package file size in bytes' - optional :terraform_module_max_file_size, type: Integer, desc: 'Maximum Terraform Module package file size in bytes' + optional :terraform_module_max_file_size, type: Integer, + desc: 'Maximum Terraform Module package file size in bytes' optional :storage_size_limit, type: Integer, desc: 'Maximum storage size for the root namespace in megabytes' end put "application/plan_limits" do diff --git a/lib/api/alert_management_alerts.rb b/lib/api/alert_management_alerts.rb index f03f133f6f7..f57b7d00c81 100644 --- a/lib/api/alert_management_alerts.rb +++ b/lib/api/alert_management_alerts.rb @@ -6,7 +6,7 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' requires :alert_iid, type: Integer, desc: 'The IID of the Alert' end diff --git a/lib/api/api.rb b/lib/api/api.rb index 933c3f69075..ffb0cdf8991 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -13,13 +13,14 @@ module API USER_REQUIREMENTS = { user_id: NO_SLASH_URL_PART_REGEX }.freeze LOG_FILTERS = ::Rails.application.config.filter_parameters + [/^output$/] LOG_FORMATTER = Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new + LOGGER = Logger.new(LOG_FILENAME) insert_before Grape::Middleware::Error, GrapeLogging::Middleware::RequestLogger, - logger: Logger.new(LOG_FILENAME), + logger: LOGGER, formatter: LOG_FORMATTER, include: [ - GrapeLogging::Loggers::FilterParameters.new(LOG_FILTERS), + Gitlab::GrapeLogging::Loggers::FilterParameters.new(LOG_FILTERS), Gitlab::GrapeLogging::Loggers::ClientEnvLogger.new, Gitlab::GrapeLogging::Loggers::RouteLogger.new, Gitlab::GrapeLogging::Loggers::UserLogger.new, @@ -168,42 +169,111 @@ module API # Mount endpoints to include in the OpenAPI V2 documentation here namespace do + # Keep in alphabetical order + mount ::API::AccessRequests + mount ::API::Admin::Ci::Variables + mount ::API::Admin::InstanceClusters + mount ::API::Admin::PlanLimits + mount ::API::Appearance + mount ::API::Applications + mount ::API::Avatar + mount ::API::Badges + mount ::API::Branches + mount ::API::BroadcastMessages + mount ::API::BulkImports + mount ::API::Ci::Jobs + mount ::API::Ci::ResourceGroups + mount ::API::Ci::Runner + mount ::API::Ci::Runners + mount ::API::Ci::Pipelines + mount ::API::Ci::PipelineSchedules + mount ::API::Ci::Triggers + mount ::API::Ci::Variables + mount ::API::Clusters::AgentTokens + mount ::API::Clusters::Agents + mount ::API::Commits + mount ::API::CommitStatuses + mount ::API::DependencyProxy + mount ::API::DeployKeys + mount ::API::DeployTokens + mount ::API::Deployments + mount ::API::Environments + mount ::API::ErrorTracking::ClientKeys + mount ::API::ErrorTracking::ProjectSettings + mount ::API::FeatureFlags + mount ::API::FeatureFlagsUserLists + mount ::API::Features + mount ::API::Files + mount ::API::FreezePeriods + mount ::API::Geo + mount ::API::GoProxy + mount ::API::GroupAvatar + mount ::API::GroupClusters + mount ::API::GroupContainerRepositories + mount ::API::GroupExport + mount ::API::GroupImport + mount ::API::GroupPackages + mount ::API::GroupVariables + mount ::API::ImportBitbucketServer + mount ::API::ImportGithub + mount ::API::Integrations + mount ::API::Invitations + mount ::API::IssueLinks + mount ::API::Keys + mount ::API::Lint + mount ::API::Markdown + mount ::API::MergeRequestApprovals + mount ::API::MergeRequestDiffs mount ::API::Metadata + mount ::API::Metrics::Dashboard::Annotations + mount ::API::Metrics::UserStarredDashboards + mount ::API::PackageFiles + mount ::API::PersonalAccessTokens::SelfInformation + mount ::API::PersonalAccessTokens + mount ::API::ProjectClusters + mount ::API::ProjectEvents + mount ::API::ProjectExport + mount ::API::ProjectHooks + mount ::API::ProjectImport + mount ::API::ProjectRepositoryStorageMoves + mount ::API::ProjectSnippets + mount ::API::ProjectSnapshots + mount ::API::ProjectStatistics + mount ::API::ProjectTemplates + mount ::API::ProtectedBranches + mount ::API::ProtectedTags + mount ::API::Releases + mount ::API::Release::Links + mount ::API::RemoteMirrors + mount ::API::Repositories + mount ::API::ResourceAccessTokens + mount ::API::ResourceMilestoneEvents + mount ::API::Snippets + mount ::API::SnippetRepositoryStorageMoves + mount ::API::Statistics + mount ::API::Submodules + mount ::API::Suggestions + mount ::API::SystemHooks + mount ::API::Tags + mount ::API::Terraform::Modules::V1::Packages + mount ::API::Terraform::State + mount ::API::Terraform::StateVersion + mount ::API::Topics + mount ::API::Unleash + mount ::API::UserCounts + mount ::API::Wikis add_open_api_documentation! end # Keep in alphabetical order - mount ::API::AccessRequests mount ::API::Admin::BatchedBackgroundMigrations - mount ::API::Admin::Ci::Variables - mount ::API::Admin::InstanceClusters - mount ::API::Admin::PlanLimits mount ::API::Admin::Sidekiq mount ::API::AlertManagementAlerts - mount ::API::Appearance - mount ::API::Applications - mount ::API::Avatar mount ::API::AwardEmoji - mount ::API::Badges mount ::API::Boards - mount ::API::Branches - mount ::API::BroadcastMessages - mount ::API::BulkImports mount ::API::Ci::JobArtifacts - mount ::API::Ci::Jobs - mount ::API::Ci::PipelineSchedules - mount ::API::Ci::Pipelines - mount ::API::Ci::ResourceGroups - mount ::API::Ci::Runner - mount ::API::Ci::Runners mount ::API::Ci::SecureFiles - mount ::API::Ci::Triggers - mount ::API::Ci::Variables - mount ::API::Clusters::Agents - mount ::API::Clusters::AgentTokens - mount ::API::CommitStatuses - mount ::API::Commits mount ::API::ComposerPackages mount ::API::ConanInstancePackages mount ::API::ConanProjectPackages @@ -211,55 +281,22 @@ module API mount ::API::ContainerRepositories mount ::API::DebianGroupPackages mount ::API::DebianProjectPackages - mount ::API::DependencyProxy - mount ::API::DeployKeys - mount ::API::DeployTokens - mount ::API::Deployments mount ::API::Discussions - mount ::API::Environments - mount ::API::ErrorTracking::ClientKeys mount ::API::ErrorTracking::Collector - mount ::API::ErrorTracking::ProjectSettings mount ::API::Events - mount ::API::FeatureFlags - mount ::API::FeatureFlagsUserLists - mount ::API::Features - mount ::API::Files - mount ::API::FreezePeriods mount ::API::GenericPackages - mount ::API::Geo - mount ::API::GoProxy - mount ::API::GroupAvatar mount ::API::GroupBoards - mount ::API::GroupClusters - mount ::API::GroupContainerRepositories mount ::API::GroupDebianDistributions - mount ::API::GroupExport - mount ::API::GroupImport mount ::API::GroupLabels mount ::API::GroupMilestones - mount ::API::GroupPackages - mount ::API::GroupVariables mount ::API::Groups mount ::API::HelmPackages - mount ::API::ImportBitbucketServer - mount ::API::ImportGithub - mount ::API::Integrations mount ::API::Integrations::JiraConnect::Subscriptions - mount ::API::Invitations - mount ::API::IssueLinks mount ::API::Issues - mount ::API::Keys mount ::API::Labels - mount ::API::Lint - mount ::API::Markdown mount ::API::MavenPackages mount ::API::Members - mount ::API::MergeRequestApprovals - mount ::API::MergeRequestDiffs mount ::API::MergeRequests - mount ::API::Metrics::Dashboard::Annotations - mount ::API::Metrics::UserStarredDashboards mount ::API::Namespaces mount ::API::Notes mount ::API::NotificationSettings @@ -267,63 +304,31 @@ module API mount ::API::NpmProjectPackages mount ::API::NugetGroupPackages mount ::API::NugetProjectPackages - mount ::API::PackageFiles mount ::API::Pages mount ::API::PagesDomains - mount ::API::PersonalAccessTokens::SelfInformation - mount ::API::PersonalAccessTokens - mount ::API::ProjectClusters mount ::API::ProjectContainerRepositories mount ::API::ProjectDebianDistributions mount ::API::ProjectEvents - mount ::API::ProjectExport - mount ::API::ProjectHooks - mount ::API::ProjectImport mount ::API::ProjectMilestones mount ::API::ProjectPackages - mount ::API::ProjectRepositoryStorageMoves - mount ::API::ProjectSnapshots - mount ::API::ProjectSnippets - mount ::API::ProjectStatistics - mount ::API::ProjectTemplates mount ::API::Projects - mount ::API::ProtectedBranches mount ::API::ProtectedTags mount ::API::PypiPackages - mount ::API::Release::Links - mount ::API::Releases - mount ::API::RemoteMirrors - mount ::API::Repositories - mount ::API::ResourceAccessTokens mount ::API::ResourceLabelEvents - mount ::API::ResourceMilestoneEvents mount ::API::ResourceStateEvents mount ::API::RpmProjectPackages mount ::API::RubygemPackages mount ::API::Search mount ::API::Settings mount ::API::SidekiqMetrics - mount ::API::SnippetRepositoryStorageMoves - mount ::API::Snippets - mount ::API::Statistics - mount ::API::Submodules mount ::API::Subscriptions - mount ::API::Suggestions - mount ::API::SystemHooks mount ::API::Tags mount ::API::Templates - mount ::API::Terraform::Modules::V1::Packages - mount ::API::Terraform::State - mount ::API::Terraform::StateVersion mount ::API::Todos - mount ::API::Topics - mount ::API::Unleash mount ::API::UsageData mount ::API::UsageDataNonSqlMetrics mount ::API::UsageDataQueries - mount ::API::UserCounts mount ::API::Users - mount ::API::Wikis mount ::API::Ml::Mlflow end diff --git a/lib/api/appearance.rb b/lib/api/appearance.rb index e599abf4aaf..69f1521ef2a 100644 --- a/lib/api/appearance.rb +++ b/lib/api/appearance.rb @@ -22,6 +22,7 @@ module API desc 'Modify appearance' do success Entities::Appearance + consumes ['multipart/form-data'] end params do optional :title, type: String, desc: 'Instance title on the sign in / sign up page' diff --git a/lib/api/applications.rb b/lib/api/applications.rb index 4048215160f..6fc9408a570 100644 --- a/lib/api/applications.rb +++ b/lib/api/applications.rb @@ -10,17 +10,21 @@ module API resource :applications do desc 'Create a new application' do detail 'This feature was introduced in GitLab 10.5' - success Entities::ApplicationWithSecret + success code: 200, model: Entities::ApplicationWithSecret end params do - requires :name, type: String, desc: 'Application name' - requires :redirect_uri, type: String, desc: 'Application redirect URI' - requires :scopes, type: String, desc: 'Application scopes', allow_blank: false + requires :name, type: String, desc: 'Name of the application.', documentation: { example: 'MyApplication' } + requires :redirect_uri, type: String, desc: 'Redirect URI of the application.', documentation: { example: 'https://redirect.uri' } + requires :scopes, type: String, + desc: 'Scopes of the application. You can specify multiple scopes by separating\ + each scope using a space', + allow_blank: false optional :confidential, type: Boolean, default: true, - desc: 'Application will be used where the client secret is confidential' + desc: 'The application is used where the client secret can be kept confidential. Native mobile apps \ + and Single Page Apps are considered non-confidential. Defaults to true if not supplied' end post do application = Doorkeeper::Application.new(declared_params) @@ -33,14 +37,19 @@ module API end desc 'Get applications' do + detail 'List all registered applications' success Entities::Application + is_array true end get do applications = ApplicationsFinder.new.execute present applications, with: Entities::Application end - desc 'Delete an application' + desc 'Delete an application' do + detail 'Delete a specific application' + success code: 204 + end params do requires :id, type: Integer, desc: 'The ID of the application (not the application_id)' end diff --git a/lib/api/badges.rb b/lib/api/badges.rb index 0a3f247ffd6..020ba53b9ee 100644 --- a/lib/api/badges.rb +++ b/lib/api/badges.rb @@ -28,6 +28,8 @@ module API desc "Gets a list of #{source_type} badges viewable by the authenticated user." do detail 'This feature was introduced in GitLab 10.6.' success Entities::Badge + is_array true + tags %w[badges] end params do use :pagination @@ -46,6 +48,7 @@ module API desc "Preview a badge from a #{source_type}." do detail 'This feature was introduced in GitLab 10.6.' success Entities::BasicBadgeDetails + tags %w[badges] end params do requires :link_url, type: String, desc: 'URL of the badge link' @@ -69,6 +72,7 @@ module API desc "Gets a badge of a #{source_type}." do detail 'This feature was introduced in GitLab 10.6.' success Entities::Badge + tags %w[badges] end params do requires :badge_id, type: Integer, desc: 'The badge ID' @@ -86,6 +90,7 @@ module API desc "Adds a badge to a #{source_type}." do detail 'This feature was introduced in GitLab 10.6.' success Entities::Badge + tags %w[badges] end params do requires :link_url, type: String, desc: 'URL of the badge link' @@ -107,6 +112,7 @@ module API desc "Updates a badge of a #{source_type}." do detail 'This feature was introduced in GitLab 10.6.' success Entities::Badge + tags %w[badges] end params do optional :link_url, type: String, desc: 'URL of the badge link' @@ -127,8 +133,9 @@ module API end end - desc 'Removes a badge from a project or group.' do + desc "Removes a badge from the #{source_type}." do detail 'This feature was introduced in GitLab 10.6.' + tags %w[badges] end params do requires :badge_id, type: Integer, desc: 'The badge ID' diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 6e3005ce676..0e0f6441da7 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -19,7 +19,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do segment ':id/boards' do diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 7e6b0214c03..845e42c2ed8 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -14,7 +14,7 @@ module API before do require_repository_enabled! - authorize! :download_code, user_project + authorize! :read_code, user_project end rescue_from Gitlab::Git::Repository::NoRepository do @@ -29,17 +29,21 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a project repository branches' do success Entities::Branch + success code: 200, model: Entities::Branch + failure [{ code: 404, message: '404 Project Not Found' }] + tags %w[branches] + is_array true end params do use :pagination use :filter_params - optional :page_token, type: String, desc: 'Name of branch to start the paginaition from' + optional :page_token, type: String, desc: 'Name of branch to start the pagination from' end get ':id/repository/branches', urgency: :low do cache_action([user_project, :branches, current_user, declared_params], expires_in: 30.seconds) do @@ -65,15 +69,23 @@ module API end resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do - desc 'Get a single branch' do - success Entities::Branch - end params do requires :branch, type: String, desc: 'The name of the branch' end + desc 'Check if a branch exists' do + success [{ code: 204, message: 'No Content' }] + failure [{ code: 404, message: 'Not Found' }] + tags %w[branches] + end head do user_project.repository.branch_exists?(params[:branch]) ? no_content! : not_found! end + desc 'Get a single repository branch' do + success Entities::Branch + success code: 200, model: Entities::Branch + failure [{ code: 404, message: 'Branch Not Found' }, { code: 404, message: 'Project Not Found' }] + tags %w[branches] + end get '/', urgency: :low do branch = find_branch!(params[:branch]) @@ -87,6 +99,9 @@ module API # but it works with the changed data model to infer `developers_can_merge` and `developers_can_push`. desc 'Protect a single branch' do success Entities::Branch + success code: 200, model: Entities::Branch + failure [{ code: 404, message: '404 Branch Not Found' }] + tags %w[branches] end params do requires :branch, type: String, desc: 'The name of the branch', allow_blank: false @@ -126,6 +141,9 @@ module API # Note: This API will be deprecated in favor of the protected branches API. desc 'Unprotect a single branch' do success Entities::Branch + success code: 200, model: Entities::Branch + failure [{ code: 404, message: '404 Project Not Found' }, { code: 404, message: '404 Branch Not Found' }] + tags %w[branches] end params do requires :branch, type: String, desc: 'The name of the branch', allow_blank: false @@ -145,6 +163,9 @@ module API desc 'Create branch' do success Entities::Branch + success code: 201, model: Entities::Branch + failure [{ code: 400, message: 'Failed to create branch' }, { code: 400, message: 'Branch already exists' }] + tags %w[branches] end params do requires :branch, type: String, desc: 'The name of the branch', allow_blank: false @@ -166,7 +187,11 @@ module API end end - desc 'Delete a branch' + desc 'Delete a branch' do + success code: 204 + failure [{ code: 404, message: 'Branch Not Found' }] + tags %w[branches] + end params do requires :branch, type: String, desc: 'The name of the branch', allow_blank: false end @@ -187,7 +212,11 @@ module API end end - desc 'Delete all merged branches' + desc 'Delete all merged branches' do + success code: 202, message: '202 Accepted' + failure [{ code: 404, message: '404 Project Not Found' }] + tags %w[branches] + end delete ':id/repository/merged_branches' do ::Branches::DeleteMergedService.new(user_project, current_user).async_execute diff --git a/lib/api/bulk_imports.rb b/lib/api/bulk_imports.rb index c54632919be..a28db321348 100644 --- a/lib/api/bulk_imports.rb +++ b/lib/api/bulk_imports.rb @@ -41,7 +41,15 @@ module API resource :bulk_imports do desc 'Start a new GitLab Migration' do detail 'This feature was introduced in GitLab 14.2.' - success Entities::BulkImport + success code: 200, model: Entities::BulkImport + consumes ['application/x-www-form-urlencoded'] + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' }, + { code: 503, message: 'Service unavailable' } + ] end params do requires :configuration, type: Hash, desc: 'The source GitLab instance configuration' do @@ -88,7 +96,13 @@ module API desc 'List all GitLab Migrations' do detail 'This feature was introduced in GitLab 14.1.' - success Entities::BulkImport + is_array true + success code: 200, model: Entities::BulkImport + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] end params do use :pagination @@ -103,7 +117,13 @@ module API desc "List all GitLab Migrations' entities" do detail 'This feature was introduced in GitLab 14.1.' - success Entities::BulkImports::Entity + is_array true + success code: 200, model: Entities::BulkImports::Entity + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] end params do use :pagination @@ -123,7 +143,12 @@ module API desc 'Get GitLab Migration details' do detail 'This feature was introduced in GitLab 14.1.' - success Entities::BulkImport + success code: 200, model: Entities::BulkImport + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] end params do requires :import_id, type: Integer, desc: "The ID of user's GitLab Migration" @@ -134,7 +159,13 @@ module API desc "List GitLab Migration entities" do detail 'This feature was introduced in GitLab 14.1.' - success Entities::BulkImports::Entity + is_array true + success code: 200, model: Entities::BulkImports::Entity + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] end params do requires :import_id, type: Integer, desc: "The ID of user's GitLab Migration" @@ -148,7 +179,12 @@ module API desc 'Get GitLab Migration entity details' do detail 'This feature was introduced in GitLab 14.1.' - success Entities::BulkImports::Entity + success code: 200, model: Entities::BulkImports::Entity + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] end params do requires :import_id, type: Integer, desc: "The ID of user's GitLab Migration" diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb index 269f2fa7ddc..be4d82bc500 100644 --- a/lib/api/ci/helpers/runner.rb +++ b/lib/api/ci/helpers/runner.rb @@ -53,7 +53,7 @@ module API # HTTP status codes to terminate the job on GitLab Runner: # - 403 - def authenticate_job!(require_running: true, heartbeat_runner: false) + def authenticate_job!(heartbeat_runner: false) job = current_job # 404 is not returned here because we want to terminate the job if it's @@ -66,10 +66,7 @@ module API forbidden!('Project has been deleted!') if job.project.nil? || job.project.pending_delete? forbidden!('Job has been erased!') if job.erased? - - if require_running - job_forbidden!(job, 'Job is not running') unless job.running? - end + job_forbidden!(job, 'Job is not running') unless job.running? # Only some requests (like updating the job or patching the trace) should trigger # runner heartbeat. Operations like artifacts uploading are executed in context of @@ -87,9 +84,9 @@ module API end def authenticate_job_via_dependent_job! - forbidden! unless current_authenticated_job + authenticate! forbidden! unless current_job - forbidden! unless can?(current_authenticated_job.user, :read_build, current_job) + forbidden! unless can?(current_user, :read_build, current_job) end def current_job @@ -106,21 +103,6 @@ module API end end - # TODO: Replace this with `#current_authenticated_job from API::Helpers` - # after the feature flag `ci_authenticate_running_job_token_for_artifacts` - # is removed. - # - # For the time being, this needs to be overridden because the API - # GET api/v4/jobs/:id/artifacts - # needs to allow requests using token whose job is not running. - # - # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83713#note_942368526 - def current_authenticated_job - strong_memoize(:current_authenticated_job) do - ::Ci::AuthJobFinder.new(token: job_token).execute - end - end - # The token used by runner to authenticate a request. # In most cases, the runner uses the token belonging to the requested job. # However, when requesting for job artifacts, the runner would use @@ -151,10 +133,6 @@ module API { config: attributes_for_keys(%w(gpus), params.dig('info', 'config')) } end - def request_using_running_job_token? - current_job.present? && current_authenticated_job.present? && current_job != current_authenticated_job - end - def metrics strong_memoize(:metrics) { ::Gitlab::Ci::Runner::Metrics.new } end diff --git a/lib/api/ci/job_artifacts.rb b/lib/api/ci/job_artifacts.rb index 37c7cc73c46..352ad04c982 100644 --- a/lib/api/ci/job_artifacts.rb +++ b/lib/api/ci/job_artifacts.rb @@ -19,7 +19,7 @@ module API prepend_mod_with('API::Ci::JobArtifacts') # rubocop: disable Cop/InjectEnterpriseEditionModule params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Download the artifacts archive from a job' do @@ -38,7 +38,7 @@ module API latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name]) authorize_read_job_artifacts!(latest_build) - present_artifacts_file!(latest_build.artifacts_file, project: latest_build.project) + present_artifacts_file!(latest_build.artifacts_file) end desc 'Download a specific file from artifacts archive from a ref' do @@ -80,7 +80,7 @@ module API build = find_build!(params[:job_id]) authorize_read_job_artifacts!(build) - present_artifacts_file!(build.artifacts_file, project: build.project) + present_artifacts_file!(build.artifacts_file) end desc 'Download a specific file from artifacts archive' do diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb index 6049993bf6f..9e41e1c0d8f 100644 --- a/lib/api/ci/jobs.rb +++ b/lib/api/ci/jobs.rb @@ -11,12 +11,12 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end helpers do params :optional_scope do - optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show', + optional :scope, type: Array[String], desc: 'The scope of builds to show', values: ::CommitStatus::AVAILABLE_STATUSES, coerce_with: ->(scope) { case scope @@ -29,12 +29,19 @@ module API else ['unknown'] end - } + }, + documentation: { example: %w[pending running] } end end desc 'Get a projects jobs' do - success Entities::Ci::Job + success code: 200, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do use :optional_scope @@ -53,10 +60,15 @@ module API # rubocop: enable CodeReuse/ActiveRecord desc 'Get a specific job of a project' do - success Entities::Ci::Job + success code: 200, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :job_id, type: Integer, desc: 'The ID of a job' + requires :job_id, type: Integer, desc: 'The ID of a job', documentation: { example: 88 } end get ':id/jobs/:job_id', urgency: :low, feature_category: :continuous_integration do authorize_read_builds! @@ -69,9 +81,16 @@ module API # TODO: We should use `present_disk_file!` and leave this implementation for backward compatibility (when build trace # is saved in the DB instead of file). But before that, we need to consider how to replace the value of # `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse. - desc 'Get a trace of a specific job of a project' + desc 'Get a trace of a specific job of a project' do + success code: 200, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + end params do - requires :job_id, type: Integer, desc: 'The ID of a job' + requires :job_id, type: Integer, desc: 'The ID of a job', documentation: { example: 88 } end get ':id/jobs/:job_id/trace', urgency: :low, feature_category: :continuous_integration do authorize_read_builds! @@ -90,10 +109,15 @@ module API end desc 'Cancel a specific job of a project' do - success Entities::Ci::Job + success code: 201, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :job_id, type: Integer, desc: 'The ID of a job' + requires :job_id, type: Integer, desc: 'The ID of a job', documentation: { example: 88 } end post ':id/jobs/:job_id/cancel', urgency: :low, feature_category: :continuous_integration do authorize_update_builds! @@ -107,10 +131,15 @@ module API end desc 'Retry a specific build of a project' do - success Entities::Ci::Job + success code: 201, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :job_id, type: Integer, desc: 'The ID of a build' + requires :job_id, type: Integer, desc: 'The ID of a build', documentation: { example: 88 } end post ':id/jobs/:job_id/retry', urgency: :low, feature_category: :continuous_integration do authorize_update_builds! @@ -128,10 +157,16 @@ module API end desc 'Erase job (remove artifacts and the trace)' do - success Entities::Ci::Job + success code: 201, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Conflict' } + ] end params do - requires :job_id, type: Integer, desc: 'The ID of a build' + requires :job_id, type: Integer, desc: 'The ID of a build', documentation: { example: 88 } end post ':id/jobs/:job_id/erase', urgency: :low, feature_category: :continuous_integration do authorize_update_builds! @@ -148,15 +183,21 @@ module API end desc 'Trigger an actionable job (manual, delayed, etc)' do - success Entities::Ci::JobBasic detail 'This feature was added in GitLab 8.11' + success code: 200, model: Entities::Ci::JobBasic + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :job_id, type: Integer, desc: 'The ID of a Job' + requires :job_id, type: Integer, desc: 'The ID of a Job', documentation: { example: 88 } optional :job_variables_attributes, type: Array, desc: 'User defined variables that will be included when running the job' do - requires :key, type: String, desc: 'The name of the variable' - requires :value, type: String, desc: 'The value of the variable' + requires :key, type: String, desc: 'The name of the variable', documentation: { example: 'foo' } + requires :value, type: String, desc: 'The value of the variable', documentation: { example: 'bar' } end end @@ -183,7 +224,12 @@ module API resource :job do desc 'Get current job using job token' do - success Entities::Ci::Job + success code: 200, model: Entities::Ci::Job + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] end route_setting :authentication, job_token_allowed: true get '', feature_category: :continuous_integration, urgency: :low do @@ -194,6 +240,12 @@ module API desc 'Get current agents' do detail 'Retrieves a list of agents for the given job token' + success code: 200, model: Entities::Ci::Job + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] end route_setting :authentication, job_token_allowed: true get '/allowed_agents', urgency: :low, feature_category: :kubernetes_management do @@ -210,7 +262,7 @@ module API .select { |_role, role_access_level| role_access_level <= user_access_level } .map(&:first) - environment = if persisted_environment = current_authenticated_job.persisted_environment + environment = if persisted_environment = current_authenticated_job.actual_persisted_environment { tier: persisted_environment.tier, slug: persisted_environment.slug } end @@ -244,6 +296,8 @@ module API # current_authenticated_job will be nil if user is using # a valid authentication (like PRIVATE-TOKEN) that is not CI_JOB_TOKEN not_found!('Job') unless current_authenticated_job + + ::Gitlab::ApplicationContext.push(job: current_authenticated_job) end end end diff --git a/lib/api/ci/pipeline_schedules.rb b/lib/api/ci/pipeline_schedules.rb index 886c3509c51..afb3754f2ae 100644 --- a/lib/api/ci/pipeline_schedules.rb +++ b/lib/api/ci/pipeline_schedules.rb @@ -11,16 +11,24 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project', + documentation: { example: 18 } end resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get all pipeline schedules' do - success Entities::Ci::PipelineSchedule + success code: 200, model: Entities::Ci::PipelineSchedule + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do use :pagination optional :scope, type: String, values: %w[active inactive], - desc: 'The scope of pipeline schedules' + desc: 'The scope of pipeline schedules', + documentation: { example: 'active' } end # rubocop: disable CodeReuse/ActiveRecord get ':id/pipeline_schedules' do @@ -33,34 +41,51 @@ module API # rubocop: enable CodeReuse/ActiveRecord desc 'Get a single pipeline schedule' do - success Entities::Ci::PipelineScheduleDetails + success code: 200, model: Entities::Ci::PipelineScheduleDetails + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } end get ':id/pipeline_schedules/:pipeline_schedule_id' do present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails, user: current_user end desc 'Get all pipelines triggered from a pipeline schedule' do - success Entities::Ci::PipelineBasic + success code: 200, model: Entities::Ci::PipelineBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule ID' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule ID', documentation: { example: 13 } end get ':id/pipeline_schedules/:pipeline_schedule_id/pipelines' do present paginate(pipeline_schedule.pipelines), with: Entities::Ci::PipelineBasic end desc 'Create a new pipeline schedule' do - success Entities::Ci::PipelineScheduleDetails + success code: 201, model: Entities::Ci::PipelineScheduleDetails + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :description, type: String, desc: 'The description of pipeline schedule' - requires :ref, type: String, desc: 'The branch/tag name will be triggered', allow_blank: false - requires :cron, type: String, desc: 'The cron' - optional :cron_timezone, type: String, default: 'UTC', desc: 'The timezone' - optional :active, type: Boolean, default: true, desc: 'The activation of pipeline schedule' + requires :description, type: String, desc: 'The description of pipeline schedule', documentation: { example: 'Test schedule pipeline' } + requires :ref, type: String, desc: 'The branch/tag name will be triggered', allow_blank: false, documentation: { example: 'develop' } + requires :cron, type: String, desc: 'The cron', documentation: { example: '* * * * *' } + optional :cron_timezone, type: String, default: 'UTC', desc: 'The timezone', documentation: { example: 'Asia/Tokyo' } + optional :active, type: Boolean, default: true, desc: 'The activation of pipeline schedule', documentation: { example: true } end post ':id/pipeline_schedules' do authorize! :create_pipeline_schedule, user_project @@ -77,15 +102,21 @@ module API end desc 'Edit a pipeline schedule' do - success Entities::Ci::PipelineScheduleDetails + success code: 200, model: Entities::Ci::PipelineScheduleDetails + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' - optional :description, type: String, desc: 'The description of pipeline schedule' - optional :ref, type: String, desc: 'The branch/tag name will be triggered' - optional :cron, type: String, desc: 'The cron' - optional :cron_timezone, type: String, desc: 'The timezone' - optional :active, type: Boolean, desc: 'The activation of pipeline schedule' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } + optional :description, type: String, desc: 'The description of pipeline schedule', documentation: { example: 'Test schedule pipeline' } + optional :ref, type: String, desc: 'The branch/tag name will be triggered', documentation: { example: 'develop' } + optional :cron, type: String, desc: 'The cron', documentation: { example: '* * * * *' } + optional :cron_timezone, type: String, desc: 'The timezone', documentation: { example: 'Asia/Tokyo' } + optional :active, type: Boolean, desc: 'The activation of pipeline schedule', documentation: { example: true } end put ':id/pipeline_schedules/:pipeline_schedule_id' do authorize! :update_pipeline_schedule, pipeline_schedule @@ -98,10 +129,16 @@ module API end desc 'Take ownership of a pipeline schedule' do - success Entities::Ci::PipelineScheduleDetails + success code: 201, model: Entities::Ci::PipelineScheduleDetails + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } end post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do authorize! :take_ownership_pipeline_schedule, pipeline_schedule @@ -114,10 +151,16 @@ module API end desc 'Delete a pipeline schedule' do - success Entities::Ci::PipelineScheduleDetails + success code: 204 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 412, message: 'Precondition Failed' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } end delete ':id/pipeline_schedules/:pipeline_schedule_id' do authorize! :admin_pipeline_schedule, pipeline_schedule @@ -127,9 +170,15 @@ module API desc 'Play a scheduled pipeline immediately' do detail 'This feature was added in GitLab 12.8' + success code: 201 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } end post ':id/pipeline_schedules/:pipeline_schedule_id/play' do authorize! :play_pipeline_schedule, pipeline_schedule @@ -145,13 +194,20 @@ module API end desc 'Create a new pipeline schedule variable' do - success Entities::Ci::Variable + success code: 201, model: Entities::Ci::Variable + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' - requires :key, type: String, desc: 'The key of the variable' - requires :value, type: String, desc: 'The value of the variable' - optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } + requires :key, type: String, desc: 'The key of the variable', documentation: { example: 'NEW_VARIABLE' } + requires :value, type: String, desc: 'The value of the variable', documentation: { example: 'new value' } + optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var', + documentation: { default: 'env_var' } end post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do authorize! :update_pipeline_schedule, pipeline_schedule @@ -166,13 +222,20 @@ module API end desc 'Edit a pipeline schedule variable' do - success Entities::Ci::Variable + success code: 200, model: Entities::Ci::Variable + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' - requires :key, type: String, desc: 'The key of the variable' - optional :value, type: String, desc: 'The value of the variable' - optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } + requires :key, type: String, desc: 'The key of the variable', documentation: { example: 'NEW_VARIABLE' } + optional :value, type: String, desc: 'The value of the variable', documentation: { example: 'new value' } + optional :variable_type, type: String, values: ::Ci::PipelineScheduleVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file', + documentation: { default: 'env_var' } end put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do authorize! :update_pipeline_schedule, pipeline_schedule @@ -185,11 +248,16 @@ module API end desc 'Delete a pipeline schedule variable' do - success Entities::Ci::Variable + success code: 202, model: Entities::Ci::Variable + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' - requires :key, type: String, desc: 'The key of the variable' + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id', documentation: { example: 13 } + requires :key, type: String, desc: 'The key of the variable', documentation: { example: 'NEW_VARIABLE' } end delete ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do authorize! :admin_pipeline_schedule, pipeline_schedule diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index 72a81330e71..c055512e54e 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -10,12 +10,17 @@ module API before { authenticate_non_get! } params do - requires :id, type: String, desc: 'The project ID' + requires :id, type: String, desc: 'The project ID or URL-encoded path', documentation: { example: 11 } end resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get all Pipelines of the project' do detail 'This feature was introduced in GitLab 8.11.' - success Entities::Ci::PipelineBasic + success status: 200, model: Entities::Ci::PipelineBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' } + ] + is_array true end helpers do @@ -31,27 +36,39 @@ module API else ['unknown'] end - } + }, + documentation: { example: %w[pending running] } end end params do use :pagination optional :scope, type: String, values: %w[running pending finished branches tags], - desc: 'The scope of pipelines' + desc: 'The scope of pipelines', + documentation: { example: 'pending' } optional :status, type: String, values: ::Ci::HasStatus::AVAILABLE_STATUSES, - desc: 'The status of pipelines' - optional :ref, type: String, desc: 'The ref of pipelines' - optional :sha, type: String, desc: 'The sha of pipelines' - optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations' - optional :username, type: String, desc: 'The username of the user who triggered pipelines' - optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ' - optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ' + desc: 'The status of pipelines', + documentation: { example: 'pending' } + optional :ref, type: String, desc: 'The ref of pipelines', + documentation: { example: 'develop' } + optional :sha, type: String, desc: 'The sha of pipelines', + documentation: { example: 'a91957a858320c0e17f3a0eca7cfacbff50ea29a' } + optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations', + documentation: { example: false } + optional :username, type: String, desc: 'The username of the user who triggered pipelines', + documentation: { example: 'root' } + optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ', + documentation: { example: '2015-12-24T15:51:21.880Z' } + optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ', + documentation: { example: '2015-12-24T15:51:21.880Z' } optional :order_by, type: String, values: ::Ci::PipelinesFinder::ALLOWED_INDEXED_COLUMNS, default: 'id', - desc: 'Order pipelines' + desc: 'Order pipelines', + documentation: { example: 'status' } optional :sort, type: String, values: %w[asc desc], default: 'desc', - desc: 'Sort pipelines' - optional :source, type: String, values: ::Ci::Pipeline.sources.keys + desc: 'Sort pipelines', + documentation: { example: 'asc' } + optional :source, type: String, values: ::Ci::Pipeline.sources.keys, + documentation: { example: 'push' } end get ':id/pipelines', urgency: :low, feature_category: :continuous_integration do authorize! :read_pipeline, user_project @@ -63,11 +80,22 @@ module API desc 'Create a new pipeline' do detail 'This feature was introduced in GitLab 8.14' - success Entities::Ci::Pipeline + success status: 201, model: Entities::Ci::Pipeline + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :ref, type: String, desc: 'Reference' - optional :variables, Array, desc: 'Array of variables available in the pipeline' + requires :ref, type: String, desc: 'Reference', + documentation: { example: 'develop' } + optional :variables, type: Array, desc: 'Array of variables available in the pipeline' do + optional :key, type: String, desc: 'The key of the variable', documentation: { example: 'UPLOAD_TO_S3' } + optional :value, type: String, desc: 'The value of the variable', documentation: { example: 'true' } + optional :variable_type, type: String, values: ::Ci::PipelineVariable.variable_types.keys, default: 'env_var', desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' + end end post ':id/pipeline', urgency: :low, feature_category: :continuous_integration do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20711') @@ -89,12 +117,18 @@ module API end end - desc 'Gets a the latest pipeline for the project branch' do + desc 'Gets the latest pipeline for the project branch' do detail 'This feature was introduced in GitLab 12.3' - success Entities::Ci::Pipeline + success status: 200, model: Entities::Ci::Pipeline + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - optional :ref, type: String, desc: 'branch ref of pipeline' + optional :ref, type: String, desc: 'Branch ref of pipeline. Uses project default branch if not specified.', + documentation: { example: 'develop' } end get ':id/pipelines/latest', urgency: :low, feature_category: :continuous_integration do authorize! :read_pipeline, latest_pipeline @@ -104,10 +138,15 @@ module API desc 'Gets a specific pipeline for the project' do detail 'This feature was introduced in GitLab 8.11' - success Entities::Ci::Pipeline + success status: 200, model: Entities::Ci::Pipeline + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end get ':id/pipelines/:pipeline_id', urgency: :low, feature_category: :continuous_integration do authorize! :read_pipeline, pipeline @@ -116,10 +155,16 @@ module API end desc 'Get pipeline jobs' do - success Entities::Ci::Job + success status: 200, model: Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } optional :include_retried, type: Boolean, default: false, desc: 'Includes retried jobs' use :optional_scope use :pagination @@ -140,10 +185,16 @@ module API end desc 'Get pipeline bridge jobs' do - success Entities::Ci::Bridge + success status: 200, model: Entities::Ci::Bridge + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } use :optional_scope use :pagination end @@ -163,10 +214,16 @@ module API desc 'Gets the variables for a given pipeline' do detail 'This feature was introduced in GitLab 11.11' - success Entities::Ci::Variable + success status: 200, model: Entities::Ci::Variable + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end get ':id/pipelines/:pipeline_id/variables', feature_category: :pipeline_authoring, urgency: :low do authorize! :read_pipeline_variable, pipeline @@ -176,10 +233,15 @@ module API desc 'Gets the test report for a given pipeline' do detail 'This feature was introduced in GitLab 13.0.' - success TestReportEntity + success status: 200, model: TestReportEntity + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end get ':id/pipelines/:pipeline_id/test_report', feature_category: :code_testing, urgency: :low do authorize! :read_build, pipeline @@ -189,10 +251,15 @@ module API desc 'Gets the test report summary for a given pipeline' do detail 'This feature was introduced in GitLab 14.2' - success TestReportSummaryEntity + success status: 200, model: TestReportSummaryEntity + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end get ':id/pipelines/:pipeline_id/test_report_summary', feature_category: :code_testing do authorize! :read_build, pipeline @@ -205,7 +272,7 @@ module API http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end delete ':id/pipelines/:pipeline_id', urgency: :low, feature_category: :continuous_integration do authorize! :destroy_pipeline, pipeline @@ -219,10 +286,15 @@ module API desc 'Retry builds in the pipeline' do detail 'This feature was introduced in GitLab 8.11.' - success Entities::Ci::Pipeline + success status: 201, model: Entities::Ci::Pipeline + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end post ':id/pipelines/:pipeline_id/retry', urgency: :low, feature_category: :continuous_integration do authorize! :update_pipeline, pipeline @@ -238,10 +310,15 @@ module API desc 'Cancel all builds in the pipeline' do detail 'This feature was introduced in GitLab 8.11.' - success Entities::Ci::Pipeline + success status: 200, model: Entities::Ci::Pipeline + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + requires :pipeline_id, type: Integer, desc: 'The pipeline ID', documentation: { example: 18 } end post ':id/pipelines/:pipeline_id/cancel', urgency: :low, feature_category: :continuous_integration do authorize! :update_pipeline, pipeline diff --git a/lib/api/ci/resource_groups.rb b/lib/api/ci/resource_groups.rb index ea6d3cc8fd4..79a9fe58a7d 100644 --- a/lib/api/ci/resource_groups.rb +++ b/lib/api/ci/resource_groups.rb @@ -5,17 +5,30 @@ module API class ResourceGroups < ::API::Base include PaginationParams + ci_resource_groups_tags = %w[ci_resource_groups] + + RESOURCE_GROUP_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS + .merge(key: API::NO_SLASH_URL_PART_REGEX) + before { authenticate! } feature_category :continuous_delivery urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, + types: [String, Integer], + desc: 'The ID or URL-encoded path of the project owned by the authenticated user' end resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get all resource groups for this project' do + desc 'Get all resource groups for a project' do success Entities::Ci::ResourceGroup + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags ci_resource_groups_tags end params do use :pagination @@ -26,27 +39,38 @@ module API present paginate(user_project.resource_groups), with: Entities::Ci::ResourceGroup end - desc 'Get a single resource group' do + desc 'Get a specific resource group' do success Entities::Ci::ResourceGroup + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags ci_resource_groups_tags end params do requires :key, type: String, desc: 'The key of the resource group' end - get ':id/resource_groups/:key' do + get ':id/resource_groups/:key', requirements: RESOURCE_GROUP_ENDPOINT_REQUIREMENTS do authorize! :read_resource_group, resource_group present resource_group, with: Entities::Ci::ResourceGroup end - desc 'List upcoming jobs of a resource group' do + desc 'List upcoming jobs for a specific resource group' do success Entities::Ci::JobBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags ci_resource_groups_tags end params do requires :key, type: String, desc: 'The key of the resource group' use :pagination end - get ':id/resource_groups/:key/upcoming_jobs' do + get ':id/resource_groups/:key/upcoming_jobs', requirements: RESOURCE_GROUP_ENDPOINT_REQUIREMENTS do authorize! :read_resource_group, resource_group authorize! :read_build, user_project @@ -57,15 +81,25 @@ module API present paginate(upcoming_processables), with: Entities::Ci::JobBasic end - desc 'Edit a resource group' do + desc 'Edit an existing resource group' do + detail "Updates an existing resource group's properties." success Entities::Ci::ResourceGroup + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags ci_resource_groups_tags end params do requires :key, type: String, desc: 'The key of the resource group' - optional :process_mode, type: String, desc: 'The process mode', - values: ::Ci::ResourceGroup.process_modes.keys + + optional :process_mode, + type: String, + desc: 'The process mode of the resource group', + values: ::Ci::ResourceGroup.process_modes.keys end - put ':id/resource_groups/:key' do + put ':id/resource_groups/:key', requirements: RESOURCE_GROUP_ENDPOINT_REQUIREMENTS do authorize! :update_resource_group, resource_group if resource_group.update(declared_params(include_missing: false)) diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 2d2dcc544f9..c7d1887638a 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -8,25 +8,38 @@ module API content_type :txt, 'text/plain' resource :runners do - desc 'Registers a new Runner' do + desc 'Register a new runner' do + detail "Register a new runner for the instance" success Entities::Ci::RunnerRegistrationDetails - http_codes [[201, 'Runner was created'], [403, 'Forbidden']] + failure [[400, 'Bad Request'], [403, 'Forbidden']] end params do requires :token, type: String, desc: 'Registration token' optional :description, type: String, desc: %q(Runner's description) - optional :maintainer_note, type: String, desc: %q(Deprecated: Use :maintenance_note instead. Runner's maintenance notes) - optional :maintenance_note, type: String, desc: %q(Runner's maintenance notes) - optional :info, type: Hash, desc: %q(Runner's metadata) - optional :active, type: Boolean, desc: 'Deprecated: Use `:paused` instead. Should runner be active' - optional :paused, type: Boolean, desc: 'Whether the runner should ignore new jobs' - optional :locked, type: Boolean, desc: 'Whether the runner should be locked for current project' + optional :maintainer_note, type: String, desc: %q(Deprecated: see `maintenance_note`) + optional :maintenance_note, type: String, + desc: %q(Free-form maintenance notes for the runner (1024 characters)) + optional :info, type: Hash, desc: %q(Runner's metadata) do + optional :name, type: String, desc: %q(Runner's name) + optional :version, type: String, desc: %q(Runner's version) + optional :revision, type: String, desc: %q(Runner's revision) + optional :platform, type: String, desc: %q(Runner's platform) + optional :architecture, type: String, desc: %q(Runner's architecture) + end + optional :active, type: Boolean, + desc: 'Deprecated: Use `paused` instead. Specifies whether the runner is allowed ' \ + 'to receive new jobs' + optional :paused, type: Boolean, desc: 'Specifies whether the runner should ignore new jobs' + optional :locked, type: Boolean, desc: 'Specifies whether the runner should be locked for the current project' optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys, - desc: 'The access_level of the runner; `not_protected` or `ref_protected`' - optional :run_untagged, type: Boolean, desc: 'Whether the runner should handle untagged jobs' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: %q(List of Runner's tags) - optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this runner handles the job' - mutually_exclusive :maintainer_note, :maintainer_note + desc: 'The access level of the runner' + optional :run_untagged, type: Boolean, desc: 'Specifies whether the runner should handle untagged jobs' + optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + desc: %q(A list of runner tags) + optional :maximum_timeout, type: Integer, + desc: 'Maximum timeout that limits the amount of time (in seconds) ' \ + 'that runners can run jobs' + mutually_exclusive :maintainer_note, :maintenance_note mutually_exclusive :active, :paused end post '/', urgency: :low, feature_category: :runner do @@ -49,11 +62,12 @@ module API end end - desc 'Deletes a registered Runner' do - http_codes [[204, 'Runner was deleted'], [403, 'Forbidden']] + desc 'Delete a registered runner' do + summary "Delete a runner by authentication token" + failure [[403, 'Forbidden']] end params do - requires :token, type: String, desc: %q(Runner's authentication token) + requires :token, type: String, desc: %q(The runner's authentication token) end delete '/', urgency: :low, feature_category: :runner do authenticate_runner! @@ -61,11 +75,12 @@ module API destroy_conditionally!(current_runner) { ::Ci::Runners::UnregisterRunnerService.new(current_runner, params[:token]).execute } end - desc 'Validates authentication credentials' do + desc 'Validate authentication credentials' do + summary "Verify authentication for a registered runner" http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']] end params do - requires :token, type: String, desc: %q(Runner's authentication token) + requires :token, type: String, desc: %q(The runner's authentication token) end post '/verify', urgency: :low, feature_category: :runner do authenticate_runner! @@ -75,6 +90,7 @@ module API desc 'Reset runner authentication token with current token' do success Entities::Ci::ResetTokenResult + failure [[403, 'Forbidden']] end params do requires :token, type: String, desc: 'The current authentication token of the runner' @@ -94,7 +110,8 @@ module API success Entities::Ci::JobRequest::Response http_codes [[201, 'Job was scheduled'], [204, 'No job for Runner'], - [403, 'Forbidden']] + [403, 'Forbidden'], + [409, 'Conflict']] end params do requires :token, type: String, desc: %q(Runner's authentication token) @@ -168,14 +185,14 @@ module API end end - desc 'Updates a job' do + desc 'Update a job' do http_codes [[200, 'Job was updated'], [202, 'Update accepted'], [400, 'Unknown parameters'], [403, 'Forbidden']] end params do - requires :token, type: String, desc: %q(Runners's authentication token) + requires :token, type: String, desc: %q(Runner's authentication token) requires :id, type: Integer, desc: %q(Job's ID) optional :state, type: String, desc: %q(Job's status: success, failed) optional :checksum, type: String, desc: %q(Job's trace CRC32 checksum) @@ -203,7 +220,7 @@ module API end end - desc 'Appends a patch to the job trace' do + desc 'Append a patch to the job trace' do http_codes [[202, 'Trace was patched'], [400, 'Missing Content-Range header'], [403, 'Forbidden'], @@ -285,14 +302,14 @@ module API end params do requires :id, type: Integer, desc: %q(Job's ID) - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware)) + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware)), documentation: { type: 'file' } optional :token, type: String, desc: %q(Job's authentication token) optional :expire_in, type: String, desc: %q(Specify when artifacts should expire) optional :artifact_type, type: String, desc: %q(The type of artifact), default: 'archive', values: ::Ci::JobArtifact.file_types.keys optional :artifact_format, type: String, desc: %q(The format of artifact), default: 'zip', values: ::Ci::JobArtifact.file_formats.keys - optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware)) + optional :metadata, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact metadata to store (generated by Multipart middleware)), documentation: { type: 'file' } end post '/:id/artifacts', feature_category: :build_artifacts, urgency: :low do not_allowed! unless Gitlab.config.artifacts.enabled @@ -317,6 +334,7 @@ module API desc 'Download the artifacts file for job' do http_codes [[200, 'Upload allowed'], + [401, 'Unauthorized'], [403, 'Forbidden'], [404, 'Artifact not found']] end @@ -325,14 +343,11 @@ module API optional :token, type: String, desc: %q(Job's authentication token) optional :direct_download, default: false, type: Boolean, desc: %q(Perform direct download from remote storage instead of proxying artifacts) end + route_setting :authentication, job_token_allowed: true get '/:id/artifacts', feature_category: :build_artifacts do - if request_using_running_job_token? - authenticate_job_via_dependent_job! - else - authenticate_job!(require_running: false) - end + authenticate_job_via_dependent_job! - present_artifacts_file!(current_job.artifacts_file, project: current_job.project, supports_direct_download: params[:direct_download]) + present_artifacts_file!(current_job.artifacts_file, supports_direct_download: params[:direct_download]) end end end diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index 4b578f8b7e5..988c3f4f566 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -10,20 +10,98 @@ module API feature_category :runner urgency :low + helpers do + params :deprecated_filter_params do + optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES, + desc: 'Deprecated: Use `type` or `status` instead. The scope of specific runners to return' + end + + params :filter_params do + optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, desc: 'The type of runners to return' + optional :paused, type: Boolean, + desc: 'Whether to include only runners that are accepting or ignoring new jobs' + optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, + desc: 'The status of runners to return' + optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + desc: 'A list of runner tags', documentation: { example: "['macos', 'shell']" } + use :pagination + end + + def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES) + return runners unless scope.present? + + unless allowed_scopes.include?(scope) + render_api_error!('Scope contains invalid value', 400) + end + + # Support deprecated scopes + if runners.respond_to?("deprecated_#{scope}") + scope = "deprecated_#{scope}" + end + + runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend + end + + def apply_filter(runners, params) + runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES) + runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES) + runners = filter_runners(runners, params[:paused] ? 'paused' : 'active', allowed_scopes: %w[paused active]) if params.include?(:paused) + runners = runners.tagged_with(params[:tag_list]) if params[:tag_list] + + runners + end + + def get_runner(id) + runner = ::Ci::Runner.find(id) + not_found!('Runner') unless runner + runner + end + + def authenticate_show_runner!(runner) + return if runner.instance_type? || current_user.admin? + + forbidden!("No access granted") unless can?(current_user, :read_runner, runner) + end + + def authenticate_update_runner!(runner) + return if current_user.admin? + + forbidden!("No access granted") unless can?(current_user, :update_runner, runner) + end + + def authenticate_delete_runner!(runner) + return if current_user.admin? + + forbidden!("Runner associated with more than one project") if runner.runner_projects.count > 1 + forbidden!("No access granted") unless can?(current_user, :delete_runner, runner) + end + + def authenticate_enable_runner!(runner) + forbidden!("Runner is a group runner") if runner.group_type? + + return if current_user.admin? + + forbidden!("Runner is locked") if runner.locked? + forbidden!("No access granted") unless can?(current_user, :assign_runner, runner) + end + + def authenticate_list_runners_jobs!(runner) + return if current_user.admin? + + forbidden!("No access granted") unless can?(current_user, :read_builds, runner) + end + end + resource :runners do desc 'Get runners available for user' do + summary 'List owned runners' success Entities::Ci::Runner + failure [[400, 'Scope contains invalid value'], [401, 'Unauthorized']] + tags %w[runners] end params do - optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, - desc: 'The scope of specific runners to show' - optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, - desc: 'The type of the runners to show' - optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' - optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' - use :pagination + use :deprecated_filter_params + use :filter_params end get do runners = current_user.ci_owned_runners @@ -34,18 +112,16 @@ module API end desc 'Get all runners - shared and specific' do + summary 'List all runners' + detail 'Get a list of all runners in the GitLab instance (specific and shared). ' \ + 'Access is restricted to users with administrator access.' success Entities::Ci::Runner + failure [[400, 'Scope contains invalid value'], [401, 'Unauthorized']] + tags %w[runners] end params do - optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES, - desc: 'The scope of specific runners to show' - optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, - desc: 'The type of the runners to show' - optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' - optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' - use :pagination + use :deprecated_filter_params + use :filter_params end get 'all' do authenticated_as_admin! @@ -58,10 +134,13 @@ module API end desc "Get runner's details" do + detail 'At least the Maintainer role is required to get runner details at the project and group level. ' \ + 'Instance-level runner details via this endpoint are available to all signed in users.' success Entities::Ci::RunnerDetails + failure [[401, 'Unauthorized'], [403, 'No access granted'], [404, 'Runner not found']] end params do - requires :id, type: Integer, desc: 'The ID of the runner' + requires :id, type: Integer, desc: 'The ID of a runner' end get ':id' do runner = get_runner(params[:id]) @@ -71,19 +150,24 @@ module API end desc "Update runner's details" do + summary "Update details of a runner" success Entities::Ci::RunnerDetails + failure [[400, 'Bad Request'], [401, 'Unauthorized'], [403, 'No access granted'], [404, 'Runner not found']] end params do - requires :id, type: Integer, desc: 'The ID of the runner' + requires :id, type: Integer, desc: 'The ID of a runner' optional :description, type: String, desc: 'The description of the runner' - optional :active, type: Boolean, desc: 'Deprecated: Use `:paused` instead. Flag indicating whether the runner is allowed to receive jobs' - optional :paused, type: Boolean, desc: 'Flag indicating whether the runner should ignore new jobs' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of tags for a runner' - optional :run_untagged, type: Boolean, desc: 'Flag indicating whether the runner can execute untagged jobs' - optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked' + optional :active, type: Boolean, desc: 'Deprecated: Use `paused` instead. Flag indicating whether the runner is allowed to receive jobs' + optional :paused, type: Boolean, desc: 'Specifies whether the runner should ignore new jobs' + optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + desc: 'The list of tags for a runner', documentation: { example: "['macos', 'shell']" } + optional :run_untagged, type: Boolean, desc: 'Specifies whether the runner can execute untagged jobs' + optional :locked, type: Boolean, desc: 'Specifies whether the runner is locked' optional :access_level, type: String, values: ::Ci::Runner.access_levels.keys, - desc: 'The access_level of the runner' - optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' + desc: 'The access level of the runner' + optional :maximum_timeout, type: Integer, + desc: 'Maximum timeout that limits the amount of time (in seconds) ' \ + 'that runners can run jobs' at_least_one_of :description, :active, :paused, :tag_list, :run_untagged, :locked, :access_level, :maximum_timeout mutually_exclusive :active, :paused end @@ -101,10 +185,15 @@ module API end desc 'Remove a runner' do + summary 'Delete a runner' success Entities::Ci::Runner + failure [[401, 'Unauthorized'], [403, 'No access granted'], + [403, 'Runner associated with more than one project'], [404, 'Runner not found'], + [412, 'Precondition Failed']] + tags %w[runners] end params do - requires :id, type: Integer, desc: 'The ID of the runner' + requires :id, type: Integer, desc: 'The ID of a runner' end delete ':id' do runner = get_runner(params[:id]) @@ -115,13 +204,19 @@ module API end desc 'List jobs running on a runner' do + summary "List runner's jobs" + detail 'List jobs that are being processed or were processed by the specified runner. ' \ + 'The list of jobs is limited to projects where the user has at least the Reporter role.' success Entities::Ci::JobBasicWithProject + failure [[401, 'Unauthorized'], [403, 'No access granted'], [404, 'Runner not found']] + tags %w[runners jobs] end params do - requires :id, type: Integer, desc: 'The ID of the runner' + requires :id, type: Integer, desc: 'The ID of a runner' optional :status, type: String, desc: 'Status of the job', values: ::Ci::Build::AVAILABLE_STATUSES - optional :order_by, type: String, desc: 'Order by `id` or not', values: ::Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS - optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by asc (ascending) or desc (descending)' + optional :order_by, type: String, desc: 'Order by `id`', values: ::Ci::RunnerJobsFinder::ALLOWED_INDEXED_COLUMNS + optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Sort by `asc` or `desc` order. ' \ + 'Specify `order_by` as well, including for `id`' use :pagination end get ':id/jobs' do @@ -143,7 +238,10 @@ module API end desc 'Reset runner authentication token' do + summary "Reset runner's authentication token" success Entities::Ci::ResetTokenResult + failure [[403, 'No access granted'], [404, 'Runner not found']] + tags %w[runners] end params do requires :id, type: Integer, desc: 'The ID of the runner' @@ -158,24 +256,24 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, + types: [String, Integer], + desc: 'The ID or URL-encoded path of the project owned by the authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before { authorize_admin_project } desc 'Get runners available for project' do + summary "List project's runners" + detail 'List all runners available in the project, including from ancestor groups ' \ + 'and any allowed shared runners.' success Entities::Ci::Runner + failure [[400, 'Scope contains invalid value'], [403, 'No access granted']] + tags %w[runners projects] end params do - optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES, - desc: 'The scope of specific runners to show' - optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, - desc: 'The type of the runners to show' - optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' - optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' - use :pagination + use :deprecated_filter_params + use :filter_params end get ':id/runners' do runners = ::Ci::Runner.owned_or_instance_wide(user_project.id) @@ -187,11 +285,16 @@ module API present paginate(runners), with: Entities::Ci::Runner end - desc 'Enable a runner for a project' do + desc 'Enable a runner in project' do + detail "Enable an available specific runner in the project." success Entities::Ci::Runner + failure [[400, 'Bad Request'], + [403, 'No access granted'], [403, 'Runner is a group runner'], [403, 'Runner is locked'], + [404, 'Runner not found']] + tags %w[runners projects] end params do - requires :runner_id, type: Integer, desc: 'The ID of the runner' + requires :runner_id, type: Integer, desc: 'The ID of a runner' end post ':id/runners' do runner = get_runner(params[:runner_id]) @@ -205,10 +308,17 @@ module API end desc "Disable project's runner" do + summary "Disable a specific runner from the project" + detail "It works only if the project isn't the only project associated with the specified runner. " \ + "If so, an error is returned. Use the call to delete a runner instead." success Entities::Ci::Runner + failure [[400, 'Bad Request'], + [403, 'Only one project associated with the runner. Please remove the runner instead'], + [404, 'Runner not found'], [412, 'Precondition Failed']] + tags %w[runners projects] end params do - requires :runner_id, type: Integer, desc: 'The ID of the runner' + requires :runner_id, type: Integer, desc: 'The ID of a runner' end # rubocop: disable CodeReuse/ActiveRecord delete ':id/runners/:runner_id' do @@ -230,16 +340,15 @@ module API before { authorize_admin_group } desc 'Get runners available for group' do + summary "List group's runners" + detail 'List all runners available in the group as well as its ancestor groups, ' \ + 'including any allowed shared runners.' success Entities::Ci::Runner + failure [[400, 'Scope contains invalid value'], [403, 'Forbidden']] + tags %w[runners groups] end params do - optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES, - desc: 'The type of the runners to show' - optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs' - optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES, - desc: 'The status of the runners to show' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show' - use :pagination + use :filter_params end get ':id/runners' do runners = ::Ci::Runner.group_or_instance_wide(user_group) @@ -252,8 +361,11 @@ module API resource :runners do before { authenticate_non_get! } - desc 'Resets runner registration token' do + desc 'Reset runner registration token' do + summary "Reset instance's runner registration token" success Entities::Ci::ResetTokenResult + failure [[403, 'Forbidden']] + tags %w[runners groups] end post 'reset_registration_token' do authorize! :update_runners_registration_token, ApplicationSetting.current @@ -269,8 +381,11 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before { authenticate_non_get! } - desc 'Resets runner registration token' do + desc 'Reset runner registration token' do + summary "Reset the runner registration token for a project" success Entities::Ci::ResetTokenResult + failure [[401, 'Unauthorized'], [403, 'Forbidden'], [404, 'Project Not Found']] + tags %w[runners projects] end post ':id/runners/reset_registration_token' do project = find_project! user_project.id @@ -287,8 +402,11 @@ module API resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before { authenticate_non_get! } - desc 'Resets runner registration token' do + desc 'Reset runner registration token' do + summary "Reset the runner registration token for a group" success Entities::Ci::ResetTokenResult + failure [[401, 'Unauthorized'], [403, 'Forbidden'], [404, 'Group Not Found']] + tags %w[runners groups] end post ':id/runners/reset_registration_token' do group = find_group! user_group.id @@ -298,72 +416,6 @@ module API present group.runners_token_with_expiration, with: Entities::Ci::ResetTokenResult end end - - helpers do - def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES) - return runners unless scope.present? - - unless allowed_scopes.include?(scope) - render_api_error!('Scope contains invalid value', 400) - end - - # Support deprecated scopes - if runners.respond_to?("deprecated_#{scope}") - scope = "deprecated_#{scope}" - end - - runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend - end - - def apply_filter(runners, params) - runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES) - runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES) - runners = filter_runners(runners, params[:paused] ? 'paused' : 'active', allowed_scopes: %w[paused active]) if params.include?(:paused) - runners = runners.tagged_with(params[:tag_list]) if params[:tag_list] - - runners - end - - def get_runner(id) - runner = ::Ci::Runner.find(id) - not_found!('Runner') unless runner - runner - end - - def authenticate_show_runner!(runner) - return if runner.instance_type? || current_user.admin? - - forbidden!("No access granted") unless can?(current_user, :read_runner, runner) - end - - def authenticate_update_runner!(runner) - return if current_user.admin? - - forbidden!("No access granted") unless can?(current_user, :update_runner, runner) - end - - def authenticate_delete_runner!(runner) - return if current_user.admin? - - forbidden!("Runner associated with more than one project") if runner.runner_projects.count > 1 - forbidden!("No access granted") unless can?(current_user, :delete_runner, runner) - end - - def authenticate_enable_runner!(runner) - forbidden!("Runner is a group runner") if runner.group_type? - - return if current_user.admin? - - forbidden!("Runner is locked") if runner.locked? - forbidden!("No access granted") unless can?(current_user, :assign_runner, runner) - end - - def authenticate_list_runners_jobs!(runner) - return if current_user.admin? - - forbidden!("No access granted") unless can?(current_user, :read_builds, runner) - end - end end end end diff --git a/lib/api/ci/secure_files.rb b/lib/api/ci/secure_files.rb index 511b6e06cd3..dd628a3413f 100644 --- a/lib/api/ci/secure_files.rb +++ b/lib/api/ci/secure_files.rb @@ -16,7 +16,7 @@ module API default_format :json params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do @@ -61,7 +61,7 @@ module API desc 'Upload a Secure File' params do requires :name, type: String, desc: 'The name of the file' - requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The secure file to be uploaded' + requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The secure file to be uploaded', documentation: { type: 'file' } end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true post ':id/secure_files' do diff --git a/lib/api/ci/triggers.rb b/lib/api/ci/triggers.rb index c49f1c9e9e1..c202d188e43 100644 --- a/lib/api/ci/triggers.rb +++ b/lib/api/ci/triggers.rb @@ -11,16 +11,26 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project', + documentation: { example: 18 } end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Trigger a GitLab project pipeline' do - success Entities::Ci::Pipeline + success code: 201, model: Entities::Ci::Pipeline + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :ref, type: String, desc: 'The commit sha or name of a branch or tag', allow_blank: false - requires :token, type: String, desc: 'The unique token of trigger or job token' - optional :variables, type: Hash, desc: 'The list of variables to be injected into build' + requires :ref, type: String, desc: 'The commit sha or name of a branch or tag', allow_blank: false, + documentation: { example: 'develop' } + requires :token, type: String, desc: 'The unique token of trigger or job token', + documentation: { example: '6d056f63e50fe6f8c5f8f4aa10edb7' } + optional :variables, type: Hash, desc: 'The list of variables to be injected into build', + documentation: { example: { VAR1: "value1", VAR2: "value2" } } end post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20758') @@ -47,7 +57,13 @@ module API end desc 'Get triggers list' do - success Entities::Trigger + success code: 200, model: Entities::Trigger + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do use :pagination @@ -64,10 +80,15 @@ module API # rubocop: enable CodeReuse/ActiveRecord desc 'Get specific trigger of a project' do - success Entities::Trigger + success code: 200, model: Entities::Trigger + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :trigger_id, type: Integer, desc: 'The trigger ID' + requires :trigger_id, type: Integer, desc: 'The trigger ID', documentation: { example: 10 } end get ':id/triggers/:trigger_id' do authenticate! @@ -80,10 +101,17 @@ module API end desc 'Create a trigger' do - success Entities::Trigger + success code: 201, model: Entities::Trigger + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :description, type: String, desc: 'The trigger description' + requires :description, type: String, desc: 'The trigger description', + documentation: { example: 'my trigger description' } end post ':id/triggers' do authenticate! @@ -100,7 +128,13 @@ module API end desc 'Update a trigger' do - success Entities::Trigger + success code: 200, model: Entities::Trigger + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do requires :trigger_id, type: Integer, desc: 'The trigger ID' @@ -123,10 +157,16 @@ module API end desc 'Delete a trigger' do - success Entities::Trigger + success code: 204 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 412, message: 'Precondition Failed' } + ] end params do - requires :trigger_id, type: Integer, desc: 'The trigger ID' + requires :trigger_id, type: Integer, desc: 'The trigger ID', documentation: { example: 10 } end delete ':id/triggers/:trigger_id' do authenticate! diff --git a/lib/api/ci/variables.rb b/lib/api/ci/variables.rb index c9e1d115d03..5a6b5987228 100644 --- a/lib/api/ci/variables.rb +++ b/lib/api/ci/variables.rb @@ -13,12 +13,13 @@ module API helpers ::API::Helpers::VariablesHelpers params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID of a project or URL-encoded NAMESPACE/PROJECT_NAME of the project owned by the authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get project variables' do success Entities::Ci::Variable + tags %w[ci_variables] end params do use :pagination @@ -28,13 +29,15 @@ module API present paginate(variables), with: Entities::Ci::Variable end - desc 'Get a specific variable from a project' do + desc 'Get the details of a single variable from a project' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Variable Not Found' }] + tags %w[ci_variables] end params do - requires :key, type: String, desc: 'The key of the variable' + requires :key, type: String, desc: 'The key of a variable' optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production' do - optional :environment_scope, type: String, desc: 'The environment scope of the variable' + optional :environment_scope, type: String, desc: 'The environment scope of a variable' end end # rubocop: disable CodeReuse/ActiveRecord @@ -48,13 +51,17 @@ module API desc 'Create a new variable in a project' do success Entities::Ci::Variable + failure [{ code: 400, message: '400 Bad Request' }] + tags %w[ci_variables] end + route_setting :log_safety, { safe: %w[key], unsafe: %w[value] } params do - requires :key, type: String, desc: 'The key of the variable' - requires :value, type: String, desc: 'The value of the variable' + requires :key, type: String, desc: 'The key of a variable' + requires :value, type: String, desc: 'The value of a variable' optional :protected, type: Boolean, desc: 'Whether the variable is protected' optional :masked, type: Boolean, desc: 'Whether the variable is masked' - optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' + optional :raw, type: Boolean, desc: 'Whether the variable will be expanded' + optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of the variable. Default: env_var' optional :environment_scope, type: String, desc: 'The environment_scope of the variable' end post ':id/variables' do @@ -73,16 +80,20 @@ module API desc 'Update an existing variable from a project' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Variable Not Found' }] + tags %w[ci_variables] end + route_setting :log_safety, { safe: %w[key], unsafe: %w[value] } params do - optional :key, type: String, desc: 'The key of the variable' - optional :value, type: String, desc: 'The value of the variable' + optional :key, type: String, desc: 'The key of a variable' + optional :value, type: String, desc: 'The value of a variable' optional :protected, type: Boolean, desc: 'Whether the variable is protected' optional :masked, type: Boolean, desc: 'Whether the variable is masked' - optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file' - optional :environment_scope, type: String, desc: 'The environment_scope of the variable' + optional :environment_scope, type: String, desc: 'The environment_scope of a variable' + optional :raw, type: Boolean, desc: 'Whether the variable will be expanded' + optional :variable_type, type: String, values: ::Ci::Variable.variable_types.keys, desc: 'The type of the variable. Default: env_var' optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production' do - optional :environment_scope, type: String, desc: 'The environment scope of the variable' + optional :environment_scope, type: String, desc: 'The environment scope of a variable' end end # rubocop: disable CodeReuse/ActiveRecord @@ -106,9 +117,11 @@ module API desc 'Delete an existing variable from a project' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Variable Not Found' }] + tags %w[ci_variables] end params do - requires :key, type: String, desc: 'The key of the variable' + requires :key, type: String, desc: 'The key of a variable' optional :filter, type: Hash, desc: 'Available filters: [environment_scope]. Example: filter[environment_scope]=production' do optional :environment_scope, type: String, desc: 'The environment scope of the variable' end diff --git a/lib/api/clusters/agent_tokens.rb b/lib/api/clusters/agent_tokens.rb index 1f9c8700d7a..f65ae465b3d 100644 --- a/lib/api/clusters/agent_tokens.rb +++ b/lib/api/clusters/agent_tokens.rb @@ -10,7 +10,7 @@ module API feature_category :kubernetes_management params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do params do @@ -18,22 +18,24 @@ module API end resource ':id/cluster_agents/:agent_id' do resource :tokens do - desc 'List agent tokens' do - detail 'This feature was introduced in GitLab 15.0.' + desc 'List tokens for an agent' do + detail 'This feature was introduced in GitLab 15.0. Returns a list of tokens for an agent.' success Entities::Clusters::AgentTokenBasic + tags %w[cluster_agents] end params do use :pagination end get do - agent = ::Clusters::AgentsFinder.new(user_project, current_user).find(params[:agent_id]) + agent_tokens = ::Clusters::AgentTokensFinder.new(user_project, current_user, params[:agent_id]).execute - present paginate(agent.agent_tokens), with: Entities::Clusters::AgentTokenBasic + present paginate(agent_tokens), with: Entities::Clusters::AgentTokenBasic end desc 'Get a single agent token' do - detail 'This feature was introduced in GitLab 15.0.' + detail 'This feature was introduced in GitLab 15.0. Gets a single agent token.' success Entities::Clusters::AgentToken + tags %w[cluster_agents] end params do requires :token_id, type: Integer, desc: 'The ID of the agent token' @@ -47,8 +49,9 @@ module API end desc 'Create an agent token' do - detail 'This feature was introduced in GitLab 15.0.' + detail 'This feature was introduced in GitLab 15.0. Creates a new token for an agent.' success Entities::Clusters::AgentTokenWithToken + tags %w[cluster_agents] end params do requires :name, type: String, desc: 'The name for the token' @@ -71,7 +74,8 @@ module API end desc 'Revoke an agent token' do - detail 'This feature was introduced in GitLab 15.0.' + detail 'This feature was introduced in GitLab 15.0. Revokes an agent token.' + tags %w[cluster_agents] end params do requires :token_id, type: Integer, desc: 'The ID of the agent token' diff --git a/lib/api/clusters/agents.rb b/lib/api/clusters/agents.rb index 2affd9680b6..62d4fb009c6 100644 --- a/lib/api/clusters/agents.rb +++ b/lib/api/clusters/agents.rb @@ -11,12 +11,13 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'List agents' do - detail 'This feature was introduced in GitLab 14.10.' + desc 'List the agents for a project' do + detail 'This feature was introduced in GitLab 14.10. Returns the list of agents registered for the project.' success Entities::Clusters::Agent + tags %w[cluster_agents] end params do use :pagination @@ -29,9 +30,10 @@ module API present paginate(agents), with: Entities::Clusters::Agent end - desc 'Get single agent' do - detail 'This feature was introduced in GitLab 14.10.' + desc 'Get details about an agent' do + detail 'This feature was introduced in GitLab 14.10. Gets a single agent details.' success Entities::Clusters::Agent + tags %w[cluster_agents] end params do requires :agent_id, type: Integer, desc: 'The ID of an agent' @@ -42,9 +44,10 @@ module API present agent, with: Entities::Clusters::Agent end - desc 'Add an agent to a project' do - detail 'This feature was introduced in GitLab 14.10.' + desc 'Register an agent with a project' do + detail 'This feature was introduced in GitLab 14.10. Registers an agent to the project.' success Entities::Clusters::Agent + tags %w[cluster_agents] end params do requires :name, type: String, desc: 'The name of the agent' @@ -61,8 +64,9 @@ module API present result[:cluster_agent], with: Entities::Clusters::Agent end - desc 'Delete an agent' do - detail 'This feature was introduced in GitLab 14.10.' + desc 'Delete a registered agent' do + detail 'This feature was introduced in GitLab 14.10. Deletes an existing agent registration.' + tags %w[cluster_agents] end params do requires :agent_id, type: Integer, desc: 'The ID of an agent' diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 7d8b58fd7b6..954b572c9b1 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -8,7 +8,7 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do include PaginationParams @@ -16,14 +16,20 @@ module API before { authenticate! } desc "Get a commit's statuses" do - success Entities::CommitStatus + success code: 200, model: Entities::CommitStatus + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true end params do - requires :sha, type: String, desc: 'The commit hash' - optional :ref, type: String, desc: 'The ref' - optional :stage, type: String, desc: 'The stage' - optional :name, type: String, desc: 'The name' - optional :all, type: String, desc: 'Show all statuses, default: false' + requires :sha, type: String, desc: 'The commit hash', documentation: { example: '18f3e63d05582537db6d183d9d557be09e1f90c8' } + optional :ref, type: String, desc: 'The ref', documentation: { example: 'develop' } + optional :stage, type: String, desc: 'The stage', documentation: { example: 'test' } + optional :name, type: String, desc: 'The name', documentation: { example: 'bundler:audit' } + optional :all, type: Boolean, desc: 'Show all statuses', documentation: { default: false } use :pagination end # rubocop: disable CodeReuse/ActiveRecord @@ -43,19 +49,32 @@ module API # rubocop: enable CodeReuse/ActiveRecord desc 'Post status to a commit' do - success Entities::CommitStatus + success code: 200, model: Entities::CommitStatus + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :sha, type: String, desc: 'The commit hash' - requires :state, type: String, desc: 'The state of the status', - values: %w(pending running success failed canceled) - optional :ref, type: String, desc: 'The ref' - optional :target_url, type: String, desc: 'The target URL to associate with this status' - optional :description, type: String, desc: 'A short description of the status' - optional :name, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"', documentation: { default: 'default' } - optional :context, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"', documentation: { default: 'default' } - optional :coverage, type: Float, desc: 'The total code coverage' - optional :pipeline_id, type: Integer, desc: 'An existing pipeline ID, when multiple pipelines on the same commit SHA have been triggered' + requires :sha, type: String, desc: 'The commit hash', + documentation: { example: '18f3e63d05582537db6d183d9d557be09e1f90c8' } + requires :state, type: String, desc: 'The state of the status', + values: %w(pending running success failed canceled), + documentation: { example: 'pending' } + optional :ref, type: String, desc: 'The ref', + documentation: { example: 'develop' } + optional :target_url, type: String, desc: 'The target URL to associate with this status', + documentation: { example: 'https://gitlab.example.com/janedoe/gitlab-foss/builds/91' } + optional :description, type: String, desc: 'A short description of the status' + optional :name, type: String, desc: 'A string label to differentiate this status from the status of other systems', + documentation: { example: 'coverage', default: 'default' } + optional :context, type: String, desc: 'A string label to differentiate this status from the status of other systems', + documentation: { example: 'coverage', default: 'default' } + optional :coverage, type: Float, desc: 'The total code coverage', + documentation: { example: 100.0 } + optional :pipeline_id, type: Integer, desc: 'An existing pipeline ID, when multiple pipelines on the same commit SHA have been triggered' end # rubocop: disable CodeReuse/ActiveRecord post ':id/statuses/:sha' do diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 50d0687ba75..63a13b83a9b 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -9,7 +9,7 @@ module API before do require_repository_enabled! - authorize! :download_code, user_project + authorize! :read_code, user_project verify_pagination_params! end @@ -27,17 +27,35 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, urgency: :low do desc 'Get a project repository commits' do - success Entities::Commit + success code: 200, model: Entities::Commit + tags %w[commits] + is_array true + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] end params do - optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used' - optional :since, type: DateTime, desc: 'Only commits after or on this date will be returned' - optional :until, type: DateTime, desc: 'Only commits before or on this date will be returned' - optional :path, type: String, desc: 'The file path' + optional :ref_name, + type: String, + desc: 'The name of a repository branch or tag, if not given the default branch is used', + documentation: { example: 'v1.1.0' } + optional :since, + type: DateTime, + desc: 'Only commits after or on this date will be returned', + documentation: { example: '2021-09-20T11:50:22.001' } + optional :until, + type: DateTime, + desc: 'Only commits before or on this date will be returned', + documentation: { example: '2021-09-20T11:50:22.001' } + optional :path, + type: String, + desc: 'The file path', + documentation: { example: 'README.md' } optional :all, type: Boolean, desc: 'Every commit will be returned' optional :with_stats, type: Boolean, desc: 'Stats about each commit will be added to the response' optional :first_parent, type: Boolean, desc: 'Only include the first parent of merges' @@ -81,40 +99,87 @@ module API end desc 'Commit multiple file changes as one commit' do - success Entities::CommitDetail + success code: 200, model: Entities::CommitDetail + tags %w[commits] + failure [ + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' } + ] detail 'This feature was introduced in GitLab 8.13' end params do - requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide either `start_branch` or `start_sha`, and optionally `start_project`.', allow_blank: false - requires :commit_message, type: String, desc: 'Commit message' - requires :actions, type: Array, desc: 'Actions to perform in commit' do - requires :action, type: String, desc: 'The action to perform, `create`, `delete`, `move`, `update`, `chmod`', values: %w[create update move delete chmod].freeze, allow_blank: false - requires :file_path, type: String, desc: 'Full path to the file. Ex. `lib/class.rb`' + requires :branch, + type: String, + desc: 'Name of the branch to commit into. To create a new branch, also provide either `start_branch` or `start_sha`, and optionally `start_project`.', + allow_blank: false, + documentation: { example: 'master' } + requires :commit_message, + type: String, + desc: 'Commit message', + documentation: { example: 'initial commit' } + requires :actions, + type: Array, + desc: 'Actions to perform in commit' do + requires :action, + type: String, + desc: 'The action to perform, `create`, `delete`, `move`, `update`, `chmod`', values: %w[create update move delete chmod].freeze, + allow_blank: false + requires :file_path, + type: String, + desc: 'Full path to the file.', + documentation: { example: 'lib/class.rb' } given action: ->(action) { action == 'move' } do - requires :previous_path, type: String, desc: 'Original full path to the file being moved. Ex. `lib/class1.rb`' + requires :previous_path, + type: String, + desc: 'Original full path to the file being moved.', + documentation: { example: 'lib/class.rb' } end given action: ->(action) { %w[create move].include? action } do - optional :content, type: String, desc: 'File content' + optional :content, + type: String, + desc: 'File content', + documentation: { example: 'Some file content' } end given action: ->(action) { action == 'update' } do - requires :content, type: String, desc: 'File content' + requires :content, + type: String, + desc: 'File content', + documentation: { example: 'Some file content' } end optional :encoding, type: String, desc: '`text` or `base64`', default: 'text', values: %w[text base64] given action: ->(action) { %w[update move delete].include? action } do - optional :last_commit_id, type: String, desc: 'Last known file commit id' + optional :last_commit_id, + type: String, + desc: 'Last known file commit id', + documentation: { example: '2695effb5807a22ff3d138d593fd856244e155e7' } end given action: ->(action) { action == 'chmod' } do requires :execute_filemode, type: Boolean, desc: 'When `true/false` enables/disables the execute flag on the file.' end end - optional :start_branch, type: String, desc: 'Name of the branch to start the new branch from' - optional :start_sha, type: String, desc: 'SHA of the commit to start the new branch from' + optional :start_branch, + type: String, + desc: 'Name of the branch to start the new branch from', + documentation: { example: 'staging' } + optional :start_sha, + type: String, + desc: 'SHA of the commit to start the new branch from', + documentation: { example: '2695effb5807a22ff3d138d593fd856244e155e7' } mutually_exclusive :start_branch, :start_sha - optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the new branch from' - optional :author_email, type: String, desc: 'Author email for commit' - optional :author_name, type: String, desc: 'Author name for commit' + optional :start_project, + types: [Integer, String], + desc: 'The ID or path of the project to start the new branch from', + documentation: { example: 1 } + optional :author_email, + type: String, + desc: 'Author email for commit', + documentation: { example: 'janedoe@example.com' } + optional :author_name, + type: String, + desc: 'Author name for commit', + documentation: { example: 'Jane Doe' } optional :stats, type: Boolean, default: true, desc: 'Include commit stats' optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch` or `start_sha`' end @@ -151,8 +216,11 @@ module API end desc 'Get a specific commit of a project' do - success Entities::CommitDetail - failure [[404, 'Commit Not Found']] + success code: 200, model: Entities::CommitDetail + tags %w[commits] + failure [ + { code: 404, message: 'Not found' } + ] end params do requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' @@ -167,7 +235,12 @@ module API end desc 'Get the diff for a specific commit of a project' do - failure [[404, 'Commit Not Found']] + success code: 200, model: Entities::Diff + tags %w[commits] + is_array true + failure [ + { code: 404, message: 'Not found' } + ] end params do requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' @@ -184,8 +257,12 @@ module API end desc "Get a commit's comments" do - success Entities::CommitNote - failure [[404, 'Commit Not Found']] + success code: 200, model: Entities::CommitNote + tags %w[commits] + is_array true + failure [ + { code: 404, message: 'Not found' } + ] end params do use :pagination @@ -202,13 +279,25 @@ module API desc 'Cherry pick commit into a branch' do detail 'This feature was introduced in GitLab 8.15' - success Entities::Commit + success code: 200, model: Entities::Commit + tags %w[commits] + failure [ + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' } + ] end params do requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag to be cherry picked' - requires :branch, type: String, desc: 'The name of the branch', allow_blank: false + requires :branch, + type: String, + desc: 'The name of the branch', + allow_blank: false, + documentation: { example: 'master' } optional :dry_run, type: Boolean, default: false, desc: "Does not commit any changes" - optional :message, type: String, desc: 'A custom commit message to use for the picked commit' + optional :message, + type: String, + desc: 'A custom commit message to use for the picked commit', + documentation: { example: 'Initial commit' } end post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do authorize_push_to_branch!(params[:branch]) @@ -248,11 +337,20 @@ module API desc 'Revert a commit in a branch' do detail 'This feature was introduced in GitLab 11.5' - success Entities::Commit + success code: 200, model: Entities::Commit + tags %w[commits] + failure [ + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' } + ] end params do requires :sha, type: String, desc: 'Commit SHA to revert' - requires :branch, type: String, desc: 'Target branch name', allow_blank: false + requires :branch, + type: String, + desc: 'Target branch name', + allow_blank: false, + documentation: { example: 'master' } optional :dry_run, type: Boolean, default: false, desc: "Does not commit any changes" end post ':id/repository/commits/:sha/revert', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do @@ -292,7 +390,12 @@ module API desc 'Get all references a commit is pushed to' do detail 'This feature was introduced in GitLab 10.6' - success Entities::BasicRef + success code: 200, model: Entities::BasicRef + tags %w[commits] + is_array true + failure [ + { code: 404, message: 'Not found' } + ] end params do requires :sha, type: String, desc: 'A commit sha' @@ -312,14 +415,28 @@ module API end desc 'Post comment to commit' do - success Entities::CommitNote + success code: 200, model: Entities::CommitNote + tags %w[commits] + failure [ + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' } + ] end params do requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to post a comment' - requires :note, type: String, desc: 'The text of the comment' - optional :path, type: String, desc: 'The file path' + requires :note, + type: String, + desc: 'The text of the comment', + documentation: { example: 'Nice code!' } + optional :path, + type: String, + desc: 'The file path', + documentation: { example: 'doc/update/5.4-to-6.0.md' } given :path do - requires :line, type: Integer, desc: 'The line number' + requires :line, + type: Integer, + desc: 'The line number', + documentation: { example: 11 } requires :line_type, type: String, values: %w[new old], default: 'new', desc: 'The type of the line' end end @@ -361,7 +478,12 @@ module API end desc 'Get Merge Requests associated with a commit' do - success Entities::MergeRequestBasic + success code: 200, model: Entities::MergeRequestBasic + tags %w[commits] + is_array true + failure [ + { code: 404, message: 'Not found' } + ] end params do requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to find Merge Requests' @@ -383,7 +505,11 @@ module API end desc "Get a commit's signature" do - success Entities::CommitSignature + success code: 200, model: Entities::CommitSignature + tags %w[commits] + failure [ + { code: 404, message: 'Not found' } + ] end params do requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' diff --git a/lib/api/concerns/packages/conan_endpoints.rb b/lib/api/concerns/packages/conan_endpoints.rb index d8c2eb4ff33..fdbffb1689b 100644 --- a/lib/api/concerns/packages/conan_endpoints.rb +++ b/lib/api/concerns/packages/conan_endpoints.rb @@ -29,7 +29,7 @@ module API CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex CONAN_REVISION_USER_CHANNEL_REGEX = Gitlab::Regex.conan_recipe_user_channel_regex - CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze + CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).uniq.freeze included do feature_category :package_registry @@ -307,7 +307,7 @@ module API end params do - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -358,7 +358,7 @@ module API end params do - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true diff --git a/lib/api/container_registry_event.rb b/lib/api/container_registry_event.rb index 66689f8d7c8..9acf2fca1b3 100644 --- a/lib/api/container_registry_event.rb +++ b/lib/api/container_registry_event.rb @@ -23,8 +23,20 @@ module API content_type :json, DOCKER_DISTRIBUTION_EVENTS_V1_JSON format :json + desc 'Receives notifications from the container registry when an operation occurs' do + detail 'This feature was introduced in GitLab 12.10' + consumes [:json, DOCKER_DISTRIBUTION_EVENTS_V1_JSON] + end params do - requires :events, type: Array + requires :events, type: Array, desc: 'Event notifications' do + requires :action, type: String, desc: 'The action to perform, `push`, `delete`', + values: %w[push delete].freeze + optional :target, type: Hash, desc: 'The target of the action' do + optional :tag, type: String, desc: 'The target tag' + optional :repository, type: String, desc: 'The target repository' + optional :digest, type: String, desc: 'Unique identifier for target image manifest' + end + end end # This endpoint is used by Docker Registry to push a set of event diff --git a/lib/api/container_repositories.rb b/lib/api/container_repositories.rb index d4fa6153a92..f2dd1fa21fd 100644 --- a/lib/api/container_repositories.rb +++ b/lib/api/container_repositories.rb @@ -14,7 +14,7 @@ module API namespace 'registry' do params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :repositories, requirements: { id: /[0-9]*/ } do desc 'Get a container repository' do diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb index 03f0f97b805..df3b6e774ae 100644 --- a/lib/api/debian_project_packages.rb +++ b/lib/api/debian_project_packages.rb @@ -34,7 +34,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end namespace ':id/packages/debian' do @@ -64,7 +64,7 @@ module API # PUT {projects|groups}/:id/packages/debian/:file_name params do - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true diff --git a/lib/api/dependency_proxy.rb b/lib/api/dependency_proxy.rb index 290a90934d7..fcf18a2792a 100644 --- a/lib/api/dependency_proxy.rb +++ b/lib/api/dependency_proxy.rb @@ -12,11 +12,18 @@ module API end params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, types: [String, Integer], + desc: 'The ID or URL-encoded path of the group owned by the authenticated user' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Deletes all dependency_proxy_blobs for a group' do - detail 'This feature was introduced in GitLab 12.10' + desc 'Purge the dependency proxy for a group' do + detail 'Schedules for deletion the cached manifests and blobs for a group.'\ + 'This endpoint requires the Owner role for the group.' + success code: 202 + failure [ + { code: 401, message: 'Unauthorized' } + ] + tags %w[dependency_proxy] end delete ':id/dependency_proxy/cache' do not_found! unless user_group.dependency_proxy_feature_available? diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index c53f4bca5a7..ffe0b6589bc 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -4,6 +4,8 @@ module API class DeployKeys < ::API::Base include PaginationParams + deploy_keys_tags = %w[deploy_keys] + before { authenticate! } feature_category :continuous_delivery @@ -21,7 +23,16 @@ module API # rubocop: enable CodeReuse/ActiveRecord end - desc 'Return all deploy keys' + desc 'List all deploy keys' do + detail 'Get a list of all deploy keys across all projects of the GitLab instance. This endpoint requires administrator access and is not available on GitLab.com.' + success Entities::DeployKey + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' } + ] + is_array true + tags deploy_keys_tags + end params do use :pagination optional :public, type: Boolean, default: false, desc: "Only return deploy keys that are public" @@ -35,13 +46,20 @@ module API end params do - requires :id, type: String, desc: 'The ID of the project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project owned by the authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before { authorize_admin_project } - desc "Get a specific project's deploy keys" do + desc 'List deploy keys for project' do + detail "Get a list of a project's deploy keys." success Entities::DeployKeysProject + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags deploy_keys_tags end params do use :pagination @@ -54,8 +72,14 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - desc 'Get single deploy key' do + desc 'Get a single deploy key' do + detail 'Get a single key.' success Entities::DeployKeysProject + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deploy_keys_tags end params do requires :key_id, type: Integer, desc: 'The ID of the deploy key' @@ -66,12 +90,19 @@ module API present key, with: Entities::DeployKeysProject end - desc 'Add new deploy key to a project' do + desc 'Add deploy key' do + detail "Creates a new deploy key for a project. If the deploy key already exists in another project, it's joined to the current project only if the original one is accessible by the same user." success Entities::DeployKeysProject + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deploy_keys_tags end params do - requires :key, type: String, desc: 'The new deploy key' - requires :title, type: String, desc: 'The name of the deploy key' + requires :key, type: String, desc: 'New deploy key' + requires :title, type: String, desc: "New deploy key's title" optional :can_push, type: Boolean, desc: "Can deploy key push to the project's repository" end # rubocop: disable CodeReuse/ActiveRecord @@ -109,12 +140,20 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - desc 'Update an existing deploy key for a project' do + desc 'Update deploy key' do + detail 'Updates a deploy key for a project.' success Entities::DeployKey + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags deploy_keys_tags end params do requires :key_id, type: Integer, desc: 'The ID of the deploy key' - optional :title, type: String, desc: 'The name of the deploy key' + optional :title, type: String, desc: "New deploy key's title" optional :can_push, type: Boolean, desc: "Can deploy key push to the project's repository" at_least_one_of :title, :can_push end @@ -143,9 +182,14 @@ module API end end - desc 'Enable a deploy key for a project' do - detail 'This feature was added in GitLab 8.11' + desc 'Enable a deploy key' do + detail 'Enables a deploy key for a project so this can be used. Returns the enabled key, with a status code 201 when successful. This feature was added in GitLab 8.11.' success Entities::DeployKey + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deploy_keys_tags end params do requires :key_id, type: Integer, desc: 'The ID of the deploy key' @@ -161,7 +205,14 @@ module API end end - desc 'Delete deploy key for a project' + desc 'Delete deploy key' do + detail "Removes a deploy key from the project. If the deploy key is used only for this project, it's deleted from the system." + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deploy_keys_tags + end params do requires :key_id, type: Integer, desc: 'The ID of the deploy key' end diff --git a/lib/api/deploy_tokens.rb b/lib/api/deploy_tokens.rb index 3955e29621f..975a65af285 100644 --- a/lib/api/deploy_tokens.rb +++ b/lib/api/deploy_tokens.rb @@ -4,6 +4,8 @@ module API class DeployTokens < ::API::Base include PaginationParams + deploy_tokens_tags = %w[deploy_tokens] + feature_category :continuous_delivery urgency :low @@ -25,9 +27,15 @@ module API end end - desc 'Return all deploy tokens' do - detail 'This feature was introduced in GitLab 12.9.' + desc 'List all deploy tokens' do + detail 'Get a list of all deploy tokens across the GitLab instance. This endpoint requires administrator access. This feature was introduced in GitLab 12.9.' success Entities::DeployToken + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' } + ] + is_array true + tags deploy_tokens_tags end params do use :pagination @@ -46,16 +54,23 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project owned by the authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do params do use :pagination use :filter_params end - desc 'List deploy tokens for a project' do - detail 'This feature was introduced in GitLab 12.9' + desc 'List project deploy tokens' do + detail "Get a list of a project's deploy tokens. This feature was introduced in GitLab 12.9." success Entities::DeployToken + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags deploy_tokens_tags end get ':id/deploy_tokens' do authorize!(:read_deploy_token, user_project) @@ -75,13 +90,19 @@ module API type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s), - desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", "write_registry", "read_package_registry", or "write_package_registry".' - optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.' + desc: 'Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, `write_registry`, `read_package_registry`, or `write_package_registry`.' + optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`).' optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`' end desc 'Create a project deploy token' do - detail 'This feature was introduced in GitLab 12.9' + detail 'Creates a new deploy token for a project. This feature was introduced in GitLab 12.9.' success Entities::DeployTokenWithToken + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deploy_tokens_tags end post ':id/deploy_tokens' do authorize!(:create_deploy_token, user_project) @@ -98,11 +119,16 @@ module API end desc 'Get a project deploy token' do - detail 'This feature was introduced in GitLab 14.9' + detail "Get a single project's deploy token by ID. This feature was introduced in GitLab 14.9." success Entities::DeployToken + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deploy_tokens_tags end params do - requires :token_id, type: Integer, desc: 'The deploy token ID' + requires :token_id, type: Integer, desc: 'The ID of the deploy token' end get ':id/deploy_tokens/:token_id' do authorize!(:read_deploy_token, user_project) @@ -113,10 +139,15 @@ module API end desc 'Delete a project deploy token' do - detail 'This feature was introduced in GitLab 12.9' + detail 'This feature was introduced in GitLab 12.9.' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deploy_tokens_tags end params do - requires :token_id, type: Integer, desc: 'The deploy token ID' + requires :token_id, type: Integer, desc: 'The ID of the deploy token' end delete ':id/deploy_tokens/:token_id' do authorize!(:destroy_deploy_token, user_project) @@ -130,16 +161,23 @@ module API end params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, types: [Integer, String], desc: 'The ID or URL-encoded path of the group owned by the authenticated user' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do params do use :pagination use :filter_params end - desc 'List deploy tokens for a group' do - detail 'This feature was introduced in GitLab 12.9' + desc 'List group deploy tokens' do + detail "Get a list of a group's deploy tokens. This feature was introduced in GitLab 12.9." success Entities::DeployToken + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags deploy_tokens_tags end get ':id/deploy_tokens' do authorize!(:read_deploy_token, user_group) @@ -154,18 +192,24 @@ module API end params do - requires :name, type: String, desc: 'The name of the deploy token' + requires :name, type: String, desc: "New deploy token's name" requires :scopes, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s), - desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", "write_registry", "read_package_registry", or "write_package_registry".' - optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.' + desc: 'Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, `write_registry`, `read_package_registry`, or `write_package_registry`' + optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`)' optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`' end desc 'Create a group deploy token' do - detail 'This feature was introduced in GitLab 12.9' + detail 'Creates a new deploy token for a group. This feature was introduced in GitLab 12.9.' success Entities::DeployTokenWithToken + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deploy_tokens_tags end post ':id/deploy_tokens' do authorize!(:create_deploy_token, user_group) @@ -182,11 +226,16 @@ module API end desc 'Get a group deploy token' do - detail 'This feature was introduced in GitLab 14.9' + detail "Get a single group's deploy token by ID. This feature was introduced in GitLab 14.9. " success Entities::DeployToken + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deploy_tokens_tags end params do - requires :token_id, type: Integer, desc: 'The deploy token ID' + requires :token_id, type: Integer, desc: 'The ID of the deploy token' end get ':id/deploy_tokens/:token_id' do authorize!(:read_deploy_token, user_group) @@ -197,10 +246,15 @@ module API end desc 'Delete a group deploy token' do - detail 'This feature was introduced in GitLab 12.9' + detail 'Removes a deploy token from the group. This feature was introduced in GitLab 12.9.' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deploy_tokens_tags end params do - requires :token_id, type: Integer, desc: 'The deploy token ID' + requires :token_id, type: Integer, desc: 'The ID of the deploy token' end delete ':id/deploy_tokens/:token_id' do authorize!(:destroy_deploy_token, user_group) diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index ee0a026d7ac..141f089b5e1 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -5,25 +5,51 @@ module API class Deployments < ::API::Base include PaginationParams + deployments_tags = %w[deployments] + before { authenticate! } feature_category :continuous_delivery urgency :low params do - requires :id, type: String, desc: 'The project ID' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project owned by the authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get all deployments of the project' do - detail 'This feature was introduced in GitLab 8.11.' + desc 'List project deployments' do + detail 'Get a list of deployments in a project. This feature was introduced in GitLab 8.11.' success Entities::Deployment + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags deployments_tags end params do use :pagination - optional :order_by, type: String, values: DeploymentsFinder::ALLOWED_SORT_VALUES, default: DeploymentsFinder::DEFAULT_SORT_VALUE, desc: 'Return deployments ordered by specified value' - optional :sort, type: String, values: DeploymentsFinder::ALLOWED_SORT_DIRECTIONS, default: DeploymentsFinder::DEFAULT_SORT_DIRECTION, desc: 'Sort by asc (ascending) or desc (descending)' - optional :updated_after, type: DateTime, desc: 'Return deployments updated after the specified date' - optional :updated_before, type: DateTime, desc: 'Return deployments updated before the specified date' + + optional :order_by, + type: String, + values: DeploymentsFinder::ALLOWED_SORT_VALUES, + default: DeploymentsFinder::DEFAULT_SORT_VALUE, + desc: 'Return deployments ordered by either one of `id`, `iid`, `created_at`, `updated_at` or `ref` fields. Default is `id`' + + optional :sort, + type: String, + values: DeploymentsFinder::ALLOWED_SORT_DIRECTIONS, + default: DeploymentsFinder::DEFAULT_SORT_DIRECTION, + desc: 'Return deployments sorted in `asc` or `desc` order. Default is `asc`' + + optional :updated_after, + type: DateTime, + desc: 'Return deployments updated after the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`)' + + optional :updated_before, + type: DateTime, + desc: 'Return deployments updated before the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`)' + optional :environment, type: String, desc: 'The name of the environment to filter deployments by' @@ -31,7 +57,7 @@ module API optional :status, type: String, values: Deployment.statuses.keys, - desc: 'The status to filter deployments by' + desc: 'The status to filter deployments by. One of `created`, `running`, `success`, `failed`, `canceled`, or `blocked`' end get ':id/deployments' do @@ -46,12 +72,17 @@ module API bad_request!(e.message) end - desc 'Gets a specific deployment' do + desc 'Get a specific deployment' do detail 'This feature was introduced in GitLab 8.11.' success Entities::DeploymentExtended + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deployments_tags end params do - requires :deployment_id, type: Integer, desc: 'The deployment ID' + requires :deployment_id, type: Integer, desc: 'The ID of the deployment' end get ':id/deployments/:deployment_id' do authorize! :read_deployment, user_project @@ -61,30 +92,36 @@ module API present deployment, with: Entities::DeploymentExtended end - desc 'Creates a new deployment' do - detail 'This feature was introduced in GitLab 12.4' + desc 'Create a deployment' do + detail 'This feature was introduced in GitLab 12.4.' success Entities::DeploymentExtended + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags deployments_tags end params do requires :environment, type: String, - desc: 'The name of the environment to deploy to' + desc: 'The name of the environment to create the deployment for' requires :sha, type: String, - desc: 'The SHA of the commit that was deployed' + desc: 'The SHA of the commit that is deployed' requires :ref, type: String, - desc: 'The name of the branch or tag that was deployed' + desc: 'The name of the branch or tag that is deployed' requires :tag, type: Boolean, - desc: 'A boolean indicating if the deployment ran for a tag' + desc: 'A boolean that indicates if the deployed ref is a tag (`true`) or not (`false`)' requires :status, type: String, - desc: 'The status of the deployment', + desc: 'The status to filter deployments by. One of `running`, `success`, `failed`, or `canceled`', values: %w[running success failed canceled] end post ':id/deployments' do @@ -96,7 +133,7 @@ module API .find_or_create_by_name(params[:environment]) unless environment.persisted? - render_validation_error!(deployment) + render_validation_error!(environment) end authorize!(:create_deployment, environment) @@ -113,14 +150,21 @@ module API end end - desc 'Updates an existing deployment' do - detail 'This feature was introduced in GitLab 12.4' + desc 'Update a deployment' do + detail 'This feature was introduced in GitLab 12.4.' success Entities::DeploymentExtended + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags deployments_tags end params do requires :status, type: String, - desc: 'The new status of the deployment', + desc: 'The new status of the deployment. One of `running`, `success`, `failed`, or `canceled`', values: %w[running success failed canceled] end put ':id/deployments/:deployment_id' do @@ -143,12 +187,17 @@ module API end end - desc 'Deletes an existing deployment' do - detail 'This feature was introduced in GitLab 15.3' - http_codes [[204, 'Deployment was deleted'], [403, 'Forbidden'], [400, 'Cannot destroy']] + desc 'Delete a specific deployment' do + detail 'Delete a specific deployment that is not currently the last deployment for an environment or in a running state. This feature was introduced in GitLab 15.3.' + http_codes [ + [204, 'Deployment destroyed'], + [403, 'Forbidden'], + [400, '"Cannot destroy running deployment" or "Deployment currently deployed to environment"'] + ] + tags deployments_tags end params do - requires :deployment_id, type: Integer, desc: 'The deployment ID' + requires :deployment_id, type: Integer, desc: 'The ID of the deployment' end delete ':id/deployments/:deployment_id' do deployment = user_project.deployments.find(params[:deployment_id]) @@ -166,13 +215,21 @@ module API helpers Helpers::MergeRequestsHelpers - desc 'Get all merge requests of a deployment' do - detail 'This feature was introduced in GitLab 12.7.' + desc 'List of merge requests associated with a deployment' do + detail 'Retrieves the list of merge requests shipped with a given deployment. This feature was introduced in GitLab 12.7.' success Entities::MergeRequestBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags deployments_tags end params do use :pagination - requires :deployment_id, type: Integer, desc: 'The deployment ID' + + requires :deployment_id, type: Integer, desc: 'The ID of the deployment' + use :merge_requests_base_params end diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb index f73e4b621ab..d3a25a076a0 100644 --- a/lib/api/discussions.rb +++ b/lib/api/discussions.rb @@ -18,17 +18,19 @@ module API Helpers::DiscussionsHelpers.feature_category_per_noteable_type.each do |noteable_type, feature_category| parent_type = noteable_type.parent_class.to_s.underscore noteables_str = noteable_type.to_s.underscore.pluralize + notable_name = noteable_type.to_s.underscore.humanize.downcase + notable_id_type = noteable_type == Commit ? String : Integer noteables_path = noteable_type == Commit ? "repository/#{noteables_str}" : noteables_str params do requires :id, type: String, desc: "The ID of a #{parent_type}" end resource parent_type.pluralize.to_sym, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc "Get a list of #{noteable_type.to_s.downcase} discussions" do + desc "Get a list of #{notable_name} discussions" do success Entities::Discussion end params do - requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' + requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}" use :pagination end @@ -41,12 +43,12 @@ module API present Discussion.build_collection(notes, noteable), with: Entities::Discussion end - desc "Get a single #{noteable_type.to_s.downcase} discussion" do + desc "Get a single #{notable_name} discussion" do success Entities::Discussion end params do requires :discussion_id, type: String, desc: 'The ID of a discussion' - requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' + requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}" end get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id", feature_category: feature_category do noteable = find_noteable(noteable_type, params[:noteable_id]) @@ -61,39 +63,44 @@ module API present discussion, with: Entities::Discussion end - desc "Create a new #{noteable_type.to_s.downcase} discussion" do + desc "Create a new #{notable_name} discussion" do success Entities::Discussion end params do - requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' + requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}" requires :body, type: String, desc: 'The content of a note' optional :created_at, type: String, desc: 'The creation date of the note' - optional :position, type: Hash do - requires :base_sha, type: String, desc: 'Base commit SHA in the source branch' - requires :start_sha, type: String, desc: 'SHA referencing commit in target branch' - requires :head_sha, type: String, desc: 'SHA referencing HEAD of this merge request' - requires :position_type, type: String, desc: 'Type of the position reference', values: %w(text image) - optional :new_path, type: String, desc: 'File path after change' - optional :new_line, type: Integer, desc: 'Line number after change' - optional :old_path, type: String, desc: 'File path before change' - optional :old_line, type: Integer, desc: 'Line number before change' - optional :width, type: Integer, desc: 'Width of the image' - optional :height, type: Integer, desc: 'Height of the image' - optional :x, type: Integer, desc: 'X coordinate in the image' - optional :y, type: Integer, desc: 'Y coordinate in the image' - - optional :line_range, type: Hash, desc: 'Multi-line start and end' do - optional :start, type: Hash do - optional :line_code, type: String, desc: 'Start line code for multi-line note' - optional :type, type: String, desc: 'Start line type for multi-line note' - optional :old_line, type: String, desc: 'Start old_line line number' - optional :new_line, type: String, desc: 'Start new_line line number' - end - optional :end, type: Hash do - optional :line_code, type: String, desc: 'End line code for multi-line note' - optional :type, type: String, desc: 'End line type for multi-line note' - optional :old_line, type: String, desc: 'End old_line line number' - optional :new_line, type: String, desc: 'End new_line line number' + + if [Commit, MergeRequest].include?(noteable_type) + optional :position, type: Hash do + requires :base_sha, type: String, desc: 'Base commit SHA in the source branch' + requires :start_sha, type: String, desc: 'SHA referencing commit in target branch' + requires :head_sha, type: String, desc: 'SHA referencing HEAD of this merge request' + requires :position_type, type: String, desc: 'Type of the position reference', values: %w(text image) + optional :new_path, type: String, desc: 'File path after change' + optional :new_line, type: Integer, desc: 'Line number after change' + optional :old_path, type: String, desc: 'File path before change' + optional :old_line, type: Integer, desc: 'Line number before change' + optional :width, type: Integer, desc: 'Width of the image' + optional :height, type: Integer, desc: 'Height of the image' + optional :x, type: Integer, desc: 'X coordinate in the image' + optional :y, type: Integer, desc: 'Y coordinate in the image' + + if noteable_type == MergeRequest + optional :line_range, type: Hash, desc: 'Multi-line start and end' do + optional :start, type: Hash do + optional :line_code, type: String, desc: 'Start line code for multi-line note' + optional :type, type: String, desc: 'Start line type for multi-line note' + optional :old_line, type: String, desc: 'Start old_line line number' + optional :new_line, type: String, desc: 'Start new_line line number' + end + optional :end, type: Hash do + optional :line_code, type: String, desc: 'End line code for multi-line note' + optional :type, type: String, desc: 'End line type for multi-line note' + optional :old_line, type: String, desc: 'End old_line line number' + optional :new_line, type: String, desc: 'End new_line line number' + end + end end end end @@ -122,12 +129,12 @@ module API end end - desc "Get comments in a single #{noteable_type.to_s.downcase} discussion" do + desc "Get comments in a single #{notable_name} discussion" do success Entities::Discussion end params do requires :discussion_id, type: String, desc: 'The ID of a discussion' - requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' + requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}" end get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes", feature_category: feature_category do noteable = find_noteable(noteable_type, params[:noteable_id]) @@ -140,11 +147,11 @@ module API present notes, with: Entities::Note end - desc "Add a comment to a #{noteable_type.to_s.downcase} discussion" do + desc "Add a comment to a #{notable_name} discussion" do success Entities::Note end params do - requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' + requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}" requires :discussion_id, type: String, desc: 'The ID of a discussion' requires :body, type: String, desc: 'The content of a note' optional :created_at, type: String, desc: 'The creation date of the note' @@ -175,11 +182,11 @@ module API end end - desc "Get a comment in a #{noteable_type.to_s.downcase} discussion" do + desc "Get a comment in a #{notable_name} discussion" do success Entities::Note end params do - requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' + requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}" requires :discussion_id, type: String, desc: 'The ID of a discussion' requires :note_id, type: Integer, desc: 'The ID of a note' end @@ -189,11 +196,11 @@ module API get_note(noteable, params[:note_id]) end - desc "Edit a comment in a #{noteable_type.to_s.downcase} discussion" do + desc "Edit a comment in a #{notable_name} discussion" do success Entities::Note end params do - requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' + requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}" requires :discussion_id, type: String, desc: 'The ID of a discussion' requires :note_id, type: Integer, desc: 'The ID of a note' optional :body, type: String, desc: 'The content of a note' @@ -210,11 +217,11 @@ module API end end - desc "Delete a comment in a #{noteable_type.to_s.downcase} discussion" do + desc "Delete a comment in a #{notable_name} discussion" do success Entities::Note end params do - requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' + requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}" requires :discussion_id, type: String, desc: 'The ID of a discussion' requires :note_id, type: Integer, desc: 'The ID of a note' end @@ -225,11 +232,11 @@ module API end if Noteable.resolvable_types.include?(noteable_type.to_s) - desc "Resolve/unresolve an existing #{noteable_type.to_s.downcase} discussion" do + desc "Resolve/unresolve an existing #{notable_name} discussion" do success Entities::Discussion end params do - requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' + requires :noteable_id, type: notable_id_type, desc: "The ID of the #{notable_name}" requires :discussion_id, type: String, desc: 'The ID of a discussion' requires :resolved, type: Boolean, desc: 'Mark discussion resolved/unresolved' end diff --git a/lib/api/entities/application.rb b/lib/api/entities/application.rb index 33514200424..c3d8f9667c3 100644 --- a/lib/api/entities/application.rb +++ b/lib/api/entities/application.rb @@ -4,10 +4,12 @@ module API module Entities class Application < Grape::Entity expose :id - expose :uid, as: :application_id - expose :name, as: :application_name - expose :redirect_uri, as: :callback_url - expose :confidential + expose :uid, as: :application_id, + documentation: { type: 'string', + example: '5832fc6e14300a0d962240a8144466eef4ee93ef0d218477e55f11cf12fc3737' } + expose :name, as: :application_name, documentation: { type: 'string', example: 'MyApplication' } + expose :redirect_uri, as: :callback_url, documentation: { type: 'string', example: 'https://redirect.uri' } + expose :confidential, documentation: { type: 'boolean', example: true } end end end diff --git a/lib/api/entities/application_statistics.rb b/lib/api/entities/application_statistics.rb index 4bcba1da464..7e75ef23675 100644 --- a/lib/api/entities/application_statistics.rb +++ b/lib/api/entities/application_statistics.rb @@ -6,47 +6,57 @@ module API include ActionView::Helpers::NumberHelper include CountHelper - expose :forks do |counts| + expose :forks, + documentation: { type: 'integer', example: 6, desc: 'Approximate number of repo forks' } do |counts| approximate_fork_count_with_delimiters(counts) end - expose :issues do |counts| + expose :issues, + documentation: { type: 'integer', example: 121, desc: 'Approximate number of issues' } do |counts| approximate_count_with_delimiters(counts, ::Issue) end - expose :merge_requests do |counts| + expose :merge_requests, + documentation: { type: 'integer', example: 49, desc: 'Approximate number of merge requests' } do |counts| approximate_count_with_delimiters(counts, ::MergeRequest) end - expose :notes do |counts| + expose :notes, + documentation: { type: 'integer', example: 6, desc: 'Approximate number of notes' } do |counts| approximate_count_with_delimiters(counts, ::Note) end - expose :snippets do |counts| + expose :snippets, + documentation: { type: 'integer', example: 4, desc: 'Approximate number of snippets' } do |counts| approximate_count_with_delimiters(counts, ::Snippet) end - expose :ssh_keys do |counts| + expose :ssh_keys, + documentation: { type: 'integer', example: 11, desc: 'Approximate number of SSH keys' } do |counts| approximate_count_with_delimiters(counts, ::Key) end - expose :milestones do |counts| + expose :milestones, + documentation: { type: 'integer', example: 3, desc: 'Approximate number of milestones' } do |counts| approximate_count_with_delimiters(counts, ::Milestone) end - expose :users do |counts| + expose :users, documentation: { type: 'integer', example: 22, desc: 'Approximate number of users' } do |counts| approximate_count_with_delimiters(counts, ::User) end - expose :projects do |counts| + expose :projects, + documentation: { type: 'integer', example: 4, desc: 'Approximate number of projects' } do |counts| approximate_count_with_delimiters(counts, ::Project) end - expose :groups do |counts| + expose :groups, + documentation: { type: 'integer', example: 1, desc: 'Approximate number of projects' } do |counts| approximate_count_with_delimiters(counts, ::Group) end - expose :active_users do |_| + expose :active_users, + documentation: { type: 'integer', example: 21, desc: 'Number of active users' } do |_| number_with_delimiter(::User.active.count) end end diff --git a/lib/api/entities/application_with_secret.rb b/lib/api/entities/application_with_secret.rb index 3e540381d89..1d0acee8624 100644 --- a/lib/api/entities/application_with_secret.rb +++ b/lib/api/entities/application_with_secret.rb @@ -4,7 +4,8 @@ module API module Entities # Use with care, this exposes the secret class ApplicationWithSecret < Entities::Application - expose :secret + expose :secret, documentation: { type: 'string', + example: 'ee1dd64b6adc89cf7e2c23099301ccc2c61b441064e9324d963c46902a85ec34' } end end end diff --git a/lib/api/entities/basic_project_details.rb b/lib/api/entities/basic_project_details.rb index e96504db53e..2585b2d0b6d 100644 --- a/lib/api/entities/basic_project_details.rb +++ b/lib/api/entities/basic_project_details.rb @@ -6,15 +6,18 @@ module API include ::API::ProjectsRelationBuilder include Gitlab::Utils::StrongMemoize - expose :default_branch_or_main, as: :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) } + expose :default_branch_or_main, documentation: { type: 'string', example: 'main' }, as: :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :read_code, project) } # Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770 - expose :topic_names, as: :tag_list - expose :topic_names, as: :topics + expose :topic_names, as: :tag_list, documentation: { type: 'string', is_array: true, example: 'tag' } + expose :topic_names, as: :topics, documentation: { type: 'string', is_array: true, example: 'topic' } - expose :ssh_url_to_repo, :http_url_to_repo, :web_url, :readme_url + expose :ssh_url_to_repo, documentation: { type: 'string', example: 'git@gitlab.example.com:gitlab/gitlab.git' } + expose :http_url_to_repo, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab.git' } + expose :web_url, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab' } + expose :readme_url, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab/blob/master/README.md' } - expose :license_url, if: :license do |project| + expose :license_url, if: :license, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab/blob/master/LICENCE' } do |project| license = project.repository.license_blob if license @@ -26,13 +29,13 @@ module API project.repository.license end - expose :avatar_url do |project, options| + expose :avatar_url, documentation: { type: 'string', example: 'http://example.com/uploads/project/avatar/3/uploads/avatar.png' } do |project, options| project.avatar_url(only_path: false) end - expose :forks_count - expose :star_count - expose :last_activity_at + expose :forks_count, documentation: { type: 'integer', example: 1 } + expose :star_count, documentation: { type: 'integer', example: 1 } + expose :last_activity_at, documentation: { type: 'dateTime', example: '2013-09-30T13:46:02Z' } expose :namespace, using: 'API::Entities::NamespaceBasic' expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes @@ -57,6 +60,8 @@ module API super end + def self.postload_relation(projects_relation, options = {}) end + private alias_method :project, :object diff --git a/lib/api/entities/basic_ref.rb b/lib/api/entities/basic_ref.rb index 79c15075d99..1b821a5b0ec 100644 --- a/lib/api/entities/basic_ref.rb +++ b/lib/api/entities/basic_ref.rb @@ -3,7 +3,8 @@ module API module Entities class BasicRef < Grape::Entity - expose :type, :name + expose :type, documentation: { type: 'string', example: 'tag' } + expose :name, documentation: { type: 'string', example: 'v1.1.0' } end end end diff --git a/lib/api/entities/basic_release_details.rb b/lib/api/entities/basic_release_details.rb index d13080f32f4..dba19b3abd7 100644 --- a/lib/api/entities/basic_release_details.rb +++ b/lib/api/entities/basic_release_details.rb @@ -5,12 +5,12 @@ module API class BasicReleaseDetails < Grape::Entity include ::API::Helpers::Presentable - expose :name - expose :tag, as: :tag_name - expose :description - expose :created_at - expose :released_at - expose :upcoming_release?, as: :upcoming_release + expose :name, documentation: { type: 'string', example: 'Release v1.0' } + expose :tag, documentation: { type: 'string', example: 'v1.0' }, as: :tag_name + expose :description, documentation: { type: 'string', example: 'Finally released v1.0' } + expose :created_at, documentation: { type: 'dateTime', example: '2019-01-03T01:56:19.539Z' } + expose :released_at, documentation: { type: 'dateTime', example: '2019-01-03T01:56:19.539Z' } + expose :upcoming_release?, documentation: { type: 'boolean' }, as: :upcoming_release end end end diff --git a/lib/api/entities/basic_repository_storage_move.rb b/lib/api/entities/basic_repository_storage_move.rb index 3ee112fb9a2..83b4f428a56 100644 --- a/lib/api/entities/basic_repository_storage_move.rb +++ b/lib/api/entities/basic_repository_storage_move.rb @@ -3,11 +3,11 @@ module API module Entities class BasicRepositoryStorageMove < Grape::Entity - expose :id - expose :created_at - expose :human_state_name, as: :state - expose :source_storage_name - expose :destination_storage_name + expose :id, documentation: { type: 'integer', example: 1 } + expose :created_at, documentation: { type: 'dateTime', example: '2020-05-07T04:27:17.234Z' } + expose :human_state_name, as: :state, documentation: { type: 'string', example: 'scheduled' } + expose :source_storage_name, documentation: { type: 'string', example: 'default' } + expose :destination_storage_name, documentation: { type: 'string', example: 'storage1' } end end end diff --git a/lib/api/entities/basic_snippet.rb b/lib/api/entities/basic_snippet.rb index 26297514798..0e9977fd81b 100644 --- a/lib/api/entities/basic_snippet.rb +++ b/lib/api/entities/basic_snippet.rb @@ -3,16 +3,30 @@ module API module Entities class BasicSnippet < Grape::Entity - expose :id, :title, :description, :visibility - expose :updated_at, :created_at - expose :project_id - expose :web_url do |snippet| + expose :id, documentation: { type: 'integer', example: 1 } + expose :title, documentation: { type: 'string', example: 'test' } + expose :description, documentation: { type: 'string', example: 'Ruby test snippet' } + expose :visibility, documentation: { type: 'string', example: 'public' } + expose :author, using: Entities::UserBasic, documentation: { type: 'Entities::UserBasic' } + expose :created_at, documentation: { type: 'dateTime', example: '2012-06-28T10:52:04Z' } + expose :updated_at, documentation: { type: 'dateTime', example: '2012-06-28T10:52:04Z' } + expose :project_id, documentation: { type: 'integer', example: 1 } + expose :web_url, documentation: { + type: 'string', example: 'http://example.com/example/example/snippets/1' + } do |snippet| Gitlab::UrlBuilder.build(snippet) end - expose :raw_url do |snippet| + expose :raw_url, documentation: { + type: 'string', example: 'http://example.com/example/example/snippets/1/raw' + } do |snippet| Gitlab::UrlBuilder.build(snippet, raw: true) end - expose :ssh_url_to_repo, :http_url_to_repo, if: ->(snippet) { snippet.repository_exists? } + expose :ssh_url_to_repo, documentation: { + type: 'string', example: 'ssh://user@gitlab.example.com/snippets/65.git' + }, if: ->(snippet) { snippet.repository_exists? } + expose :http_url_to_repo, documentation: { + type: 'string', example: 'https://gitlab.example.com/snippets/65.git' + }, if: ->(snippet) { snippet.repository_exists? } end end end diff --git a/lib/api/entities/branch.rb b/lib/api/entities/branch.rb index 6a75dcddeda..01eaf5c8d31 100644 --- a/lib/api/entities/branch.rb +++ b/lib/api/entities/branch.rb @@ -5,13 +5,17 @@ module API class Branch < Grape::Entity include Gitlab::Routing - expose :name + expose :name, documentation: { type: 'string', example: 'master' } expose :commit, using: Entities::Commit do |repo_branch, options| options[:project].repository.commit(repo_branch.dereferenced_target) end - expose :merged do |repo_branch, options| + expose :merged, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| if options[:merged_branch_names] options[:merged_branch_names].include?(repo_branch.name) else @@ -19,27 +23,51 @@ module API end end - expose :protected do |repo_branch, options| + expose :protected, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| ::ProtectedBranch.protected?(options[:project], repo_branch.name) end - expose :developers_can_push do |repo_branch, options| + expose :developers_can_push, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| ::ProtectedBranch.developers_can?(:push, repo_branch.name, protected_refs: options[:project].protected_branches) end - expose :developers_can_merge do |repo_branch, options| + expose :developers_can_merge, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| ::ProtectedBranch.developers_can?(:merge, repo_branch.name, protected_refs: options[:project].protected_branches) end - expose :can_push do |repo_branch, options| + expose :can_push, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| Gitlab::UserAccess.new(options[:current_user], container: options[:project]).can_push_to_branch?(repo_branch.name) end - expose :default do |repo_branch, options| + expose :default, + documentation: { + type: 'boolean', + example: true + } do |repo_branch, options| options[:project].default_branch == repo_branch.name end - expose :web_url do |repo_branch| + expose :web_url, + documentation: { + type: 'string', + example: 'https://gitlab.example.com/Commit921/the-dude/-/tree/master' + } do |repo_branch| project_tree_url(options[:project], repo_branch.name) end end diff --git a/lib/api/entities/bulk_import.rb b/lib/api/entities/bulk_import.rb index 373ae486dcf..75989cb4180 100644 --- a/lib/api/entities/bulk_import.rb +++ b/lib/api/entities/bulk_import.rb @@ -3,11 +3,13 @@ module API module Entities class BulkImport < Grape::Entity - expose :id - expose :status_name, as: :status - expose :source_type - expose :created_at - expose :updated_at + expose :id, documentation: { type: 'integer', example: 1 } + expose :status_name, as: :status, documentation: { + type: 'string', example: 'finished', values: %w[created started finished timeout failed] + } + expose :source_type, documentation: { type: 'string', example: 'gitlab' } + expose :created_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :updated_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } end end end diff --git a/lib/api/entities/bulk_imports/entity.rb b/lib/api/entities/bulk_imports/entity.rb index 142bfaf2149..8f9fbe57935 100644 --- a/lib/api/entities/bulk_imports/entity.rb +++ b/lib/api/entities/bulk_imports/entity.rb @@ -4,19 +4,21 @@ module API module Entities module BulkImports class Entity < Grape::Entity - expose :id - expose :bulk_import_id - expose :status_name, as: :status - expose :source_full_path - expose :destination_name # deprecated - expose :destination_slug - expose :destination_namespace - expose :parent_id - expose :namespace_id - expose :project_id - expose :created_at - expose :updated_at - expose :failures, using: EntityFailure + expose :id, documentation: { type: 'integer', example: 1 } + expose :bulk_import_id, documentation: { type: 'integer', example: 1 } + expose :status_name, as: :status, documentation: { + type: 'string', example: 'created', values: %w[created started finished timeout failed] + } + expose :source_full_path, documentation: { type: 'string', example: 'source_group' } + expose :destination_name, documentation: { type: 'string', example: 'destination_slug' } # deprecated + expose :destination_slug, documentation: { type: 'string', example: 'destination_slug' } + expose :destination_namespace, documentation: { type: 'string', example: 'destination_path' } + expose :parent_id, documentation: { type: 'integer', example: 1 } + expose :namespace_id, documentation: { type: 'integer', example: 1 } + expose :project_id, documentation: { type: 'integer', example: 1 } + expose :created_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :updated_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :failures, using: EntityFailure, documentation: { is_array: true } end end end diff --git a/lib/api/entities/bulk_imports/entity_failure.rb b/lib/api/entities/bulk_imports/entity_failure.rb index 56312745868..3e69e7fa2aa 100644 --- a/lib/api/entities/bulk_imports/entity_failure.rb +++ b/lib/api/entities/bulk_imports/entity_failure.rb @@ -4,16 +4,18 @@ module API module Entities module BulkImports class EntityFailure < Grape::Entity - expose :relation - expose :pipeline_step, as: :step - expose :exception_message do |failure| + expose :relation, documentation: { type: 'string', example: 'group' } + expose :pipeline_step, as: :step, documentation: { type: 'string', example: 'extractor' } + expose :exception_message, documentation: { type: 'string', example: 'error message' } do |failure| ::Projects::ImportErrorFilter.filter_message(failure.exception_message.truncate(72)) end - expose :exception_class - expose :correlation_id_value - expose :created_at - expose :pipeline_class - expose :pipeline_step + expose :exception_class, documentation: { type: 'string', example: 'Exception' } + expose :correlation_id_value, documentation: { type: 'string', example: 'dfcf583058ed4508e4c7c617bd7f0edd' } + expose :created_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :pipeline_class, documentation: { + type: 'string', example: 'BulkImports::Groups::Pipelines::GroupPipeline' + } + expose :pipeline_step, documentation: { type: 'string', example: 'extractor' } end end end diff --git a/lib/api/entities/bulk_imports/export_status.rb b/lib/api/entities/bulk_imports/export_status.rb index c9c7f34a16a..fee983c6fd8 100644 --- a/lib/api/entities/bulk_imports/export_status.rb +++ b/lib/api/entities/bulk_imports/export_status.rb @@ -4,10 +4,10 @@ module API module Entities module BulkImports class ExportStatus < Grape::Entity - expose :relation - expose :status - expose :error - expose :updated_at + expose :relation, documentation: { type: 'string', example: 'issues' } + expose :status, documentation: { type: 'string', example: 'started', values: %w[started finished failed] } + expose :error, documentation: { type: 'string', example: 'Error message' } + expose :updated_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } end end end diff --git a/lib/api/entities/ci/job.rb b/lib/api/entities/ci/job.rb index cf87684ce55..d9e6b7eed75 100644 --- a/lib/api/entities/ci/job.rb +++ b/lib/api/entities/ci/job.rb @@ -6,10 +6,17 @@ module API class Job < JobBasic # artifacts_file is included in job_artifacts, but kept for backward compatibility (remove in api/v5) expose :artifacts_file, using: ::API::Entities::Ci::JobArtifactFile, if: -> (job, opts) { job.artifacts? } - expose :job_artifacts, as: :artifacts, using: ::API::Entities::Ci::JobArtifact + expose :job_artifacts, as: :artifacts, + using: ::API::Entities::Ci::JobArtifact, + documentation: { is_array: true } expose :runner, with: ::API::Entities::Ci::Runner - expose :artifacts_expire_at - expose :tag_list do |job| + expose :artifacts_expire_at, + documentation: { type: 'dateTime', example: '2016-01-19T09:05:50.355Z' } + + expose( + :tag_list, + documentation: { type: 'string', is_array: true, example: ['ubuntu18', 'docker runner'] } + ) do |job| job.tags.map(&:name).sort end end diff --git a/lib/api/entities/ci/job_artifact.rb b/lib/api/entities/ci/job_artifact.rb index 9e504aee383..8276c0f4073 100644 --- a/lib/api/entities/ci/job_artifact.rb +++ b/lib/api/entities/ci/job_artifact.rb @@ -4,7 +4,12 @@ module API module Entities module Ci class JobArtifact < Grape::Entity - expose :file_type, :size, :filename, :file_format + expose :file_type, + documentation: { type: 'string', values: ::Ci::JobArtifact.file_types.keys, example: 'archive' } + expose :size, documentation: { type: 'integer', example: 1000 } + expose :filename, documentation: { type: 'string', example: 'artifacts.zip' } + expose :file_format, + documentation: { type: 'string', values: ::Ci::JobArtifact.file_formats.keys, example: 'zip' } end end end diff --git a/lib/api/entities/ci/job_artifact_file.rb b/lib/api/entities/ci/job_artifact_file.rb index 418eb408ab6..0266f99cd6d 100644 --- a/lib/api/entities/ci/job_artifact_file.rb +++ b/lib/api/entities/ci/job_artifact_file.rb @@ -4,8 +4,8 @@ module API module Entities module Ci class JobArtifactFile < Grape::Entity - expose :filename - expose :cached_size, as: :size + expose :filename, documentation: { type: 'string', example: 'artifacts.zip' } + expose :cached_size, as: :size, documentation: { type: 'integer', example: 1000 } end end end diff --git a/lib/api/entities/ci/job_basic.rb b/lib/api/entities/ci/job_basic.rb index fb975475cf5..3cbb8aad313 100644 --- a/lib/api/entities/ci/job_basic.rb +++ b/lib/api/entities/ci/job_basic.rb @@ -4,23 +4,36 @@ module API module Entities module Ci class JobBasic < Grape::Entity - expose :id, :status, :stage, :name, :ref, :tag, :coverage, :allow_failure - expose :created_at, :started_at, :finished_at + expose :id, documentation: { type: 'integer', example: 1 } + expose :status, documentation: { type: 'string', example: 'waiting_for_resource' } + expose :stage, documentation: { type: 'string', example: 'deploy' } + expose :name, documentation: { type: 'string', example: 'deploy_to_production' } + expose :ref, documentation: { type: 'string', example: 'main' } + expose :tag, documentation: { type: 'boolean' } + expose :coverage, documentation: { type: 'number', format: 'float', example: 98.29 } + expose :allow_failure, documentation: { type: 'boolean' } + expose :created_at, documentation: { type: 'dateTime', example: '2015-12-24T15:51:21.880Z' } + expose :started_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:30.733Z' } + expose :finished_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:31.198Z' } expose :duration, - documentation: { type: 'Floating', desc: 'Time spent running' } + documentation: { type: 'number', format: 'float', desc: 'Time spent running', example: 0.465 } expose :queued_duration, - documentation: { type: 'Floating', desc: 'Time spent enqueued' } + documentation: { type: 'number', format: 'float', desc: 'Time spent enqueued', example: 0.123 } expose :user, with: ::API::Entities::User expose :commit, with: ::API::Entities::Commit expose :pipeline, with: ::API::Entities::Ci::PipelineBasic - expose :failure_reason, if: -> (job) { job.failed? } + expose :failure_reason, + documentation: { type: 'string', example: 'script_failure' }, if: -> (job) { job.failed? } - expose :web_url do |job, _options| + expose( + :web_url, + documentation: { type: 'string', example: 'https://example.com/foo/bar/-/jobs/1' } + ) do |job, _options| Gitlab::Routing.url_helpers.project_job_url(job.project, job) end expose :project do - expose :ci_job_token_scope_enabled do |job| + expose :ci_job_token_scope_enabled, documentation: { type: 'string', example: false } do |job| job.project.ci_outbound_job_token_scope_enabled? end end diff --git a/lib/api/entities/ci/lint/result.rb b/lib/api/entities/ci/lint/result.rb index b44a6e13463..698b02d3b4a 100644 --- a/lib/api/entities/ci/lint/result.rb +++ b/lib/api/entities/ci/lint/result.rb @@ -5,12 +5,17 @@ module API module Ci module Lint class Result < Grape::Entity - expose :valid?, as: :valid - expose :errors - expose :warnings - expose :merged_yaml - expose :includes - expose :jobs, if: -> (result, options) { options[:include_jobs] } + expose :valid?, as: :valid, documentation: { type: 'boolean' } + expose :errors, documentation: { is_array: true, type: 'string', + example: 'variables config should be a hash of key value pairs' } + expose :warnings, documentation: { is_array: true, type: 'string', + example: 'jobs:job may allow multiple pipelines ...' } + expose :merged_yaml, documentation: { type: 'string', example: '---\n:another_test:\n :stage: test\n + :script: echo 2\n:test:\n :stage: test\n :script: echo 1\n' } + expose :includes, documentation: { is_array: true, type: 'object', + example: '{ "blob": "https://gitlab.com/root/example-project/-/blob/...' } + expose :jobs, if: -> (result, options) { options[:include_jobs] }, + documentation: { is_array: true, type: 'object', example: '{ "name": "test: .... }' } end end end diff --git a/lib/api/entities/ci/pipeline.rb b/lib/api/entities/ci/pipeline.rb index a8033a21044..7631cf60dbd 100644 --- a/lib/api/entities/ci/pipeline.rb +++ b/lib/api/entities/ci/pipeline.rb @@ -4,13 +4,21 @@ module API module Entities module Ci class Pipeline < PipelineBasic - expose :before_sha, :tag, :yaml_errors + expose :before_sha, documentation: { type: 'string', example: 'a91957a858320c0e17f3a0eca7cfacbff50ea29a' } + expose :tag, documentation: { type: 'boolean', example: false } + expose :yaml_errors, documentation: { type: 'string', example: "widgets:build: needs 'widgets:test'" } expose :user, with: Entities::UserBasic - expose :created_at, :updated_at, :started_at, :finished_at, :committed_at - expose :duration - expose :queued_duration - expose :coverage do |pipeline| + expose :created_at, documentation: { type: 'dateTime', example: '2015-12-24T15:51:21.880Z' } + expose :updated_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:31.198Z' } + expose :started_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:30.733Z' } + expose :finished_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:31.198Z' } + expose :committed_at, documentation: { type: 'dateTime', example: '2015-12-24T15:51:21.880Z' } + expose :duration, + documentation: { type: 'integer', desc: 'Time spent running in seconds', example: 127 } + expose :queued_duration, + documentation: { type: 'integer', desc: 'Time spent enqueued in seconds', example: 63 } + expose :coverage, documentation: { type: 'number', format: 'float', example: 98.29 } do |pipeline| pipeline.present.coverage end expose :detailed_status, using: DetailedStatusEntity do |pipeline, options| diff --git a/lib/api/entities/ci/pipeline_basic.rb b/lib/api/entities/ci/pipeline_basic.rb index a2a5a98920a..6d82cca1bf1 100644 --- a/lib/api/entities/ci/pipeline_basic.rb +++ b/lib/api/entities/ci/pipeline_basic.rb @@ -4,10 +4,21 @@ module API module Entities module Ci class PipelineBasic < Grape::Entity - expose :id, :iid, :project_id, :sha, :ref, :status, :source - expose :created_at, :updated_at + expose :id, documentation: { type: 'integer', example: 1 } + expose :iid, documentation: { type: 'integer', example: 2 } + expose :project_id, documentation: { type: 'integer', example: 3 } + expose :sha, documentation: { type: 'string', example: '0ec9e58fdfca6cdd6652c083c9edb53abc0bad52' } + expose :ref, documentation: { type: 'string', example: 'feature-branch' } + expose :status, documentation: { type: 'string', example: 'success' } + expose :source, documentation: { type: 'string', example: 'push' } + expose :created_at, documentation: { type: 'dateTime', example: '2022-10-21T16:49:48.000+02:00' } + expose :updated_at, documentation: { type: 'dateTime', example: '2022-10-21T16:49:48.000+02:00' } - expose :web_url do |pipeline, _options| + expose :web_url, + documentation: { + type: 'string', + example: 'https://gitlab.example.com/gitlab-org/gitlab-foss/-/pipelines/61' + } do |pipeline, _options| Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline) end end diff --git a/lib/api/entities/ci/pipeline_schedule.rb b/lib/api/entities/ci/pipeline_schedule.rb index f1596b7d285..58496bded03 100644 --- a/lib/api/entities/ci/pipeline_schedule.rb +++ b/lib/api/entities/ci/pipeline_schedule.rb @@ -4,9 +4,15 @@ module API module Entities module Ci class PipelineSchedule < Grape::Entity - expose :id - expose :description, :ref, :cron, :cron_timezone, :next_run_at, :active - expose :created_at, :updated_at + expose :id, documentation: { type: 'integer', example: 13 } + expose :description, documentation: { type: 'string', example: 'Test schedule pipeline' } + expose :ref, documentation: { type: 'string', example: 'develop' } + expose :cron, documentation: { type: 'string', example: '* * * * *' } + expose :cron_timezone, documentation: { type: 'string', example: 'Asia/Tokyo' } + expose :next_run_at, documentation: { type: 'dateTime', example: '2017-05-19T13:41:00.000Z' } + expose :active, documentation: { type: 'boolean', example: true } + expose :created_at, documentation: { type: 'dateTime', example: '2017-05-19T13:31:08.849Z' } + expose :updated_at, documentation: { type: 'dateTime', example: '2017-05-19T13:40:17.727Z' } expose :owner, using: ::API::Entities::UserBasic end end diff --git a/lib/api/entities/ci/resource_group.rb b/lib/api/entities/ci/resource_group.rb index 0afadfa9e2a..c14e32d32b1 100644 --- a/lib/api/entities/ci/resource_group.rb +++ b/lib/api/entities/ci/resource_group.rb @@ -4,7 +4,11 @@ module API module Entities module Ci class ResourceGroup < Grape::Entity - expose :id, :key, :process_mode, :created_at, :updated_at + expose :id, documentation: { type: 'integer', example: 1 } + expose :key, documentation: { type: 'string', example: 'production' } + expose :process_mode, documentation: { type: 'string', example: 'unordered' } + expose :created_at, documentation: { type: 'dateTime', example: '2021-09-01T08:04:59.650Z' } + expose :updated_at, documentation: { type: 'dateTime', example: '2021-09-01T08:04:59.650Z' } end end end diff --git a/lib/api/entities/ci/runner.rb b/lib/api/entities/ci/runner.rb index f034eb5c94c..9361709b6ed 100644 --- a/lib/api/entities/ci/runner.rb +++ b/lib/api/entities/ci/runner.rb @@ -4,20 +4,22 @@ module API module Entities module Ci class Runner < Grape::Entity - expose :id - expose :description - expose :ip_address - expose :active # TODO Remove in v5 in favor of `paused` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/375709 - expose :paused do |runner| + expose :id, documentation: { type: 'integer', example: 8 } + expose :description, documentation: { type: 'string', example: 'test-1-20150125' } + expose :ip_address, documentation: { type: 'string', example: '127.0.0.1' } + # TODO Remove in v5 in favor of `paused` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/375709 + expose :active, documentation: { type: 'boolean', example: true } + expose :paused, documentation: { type: 'boolean', example: false } do |runner| !runner.active end - expose :instance_type?, as: :is_shared - expose :runner_type - expose :name - expose :online?, as: :online + expose :instance_type?, as: :is_shared, documentation: { type: 'boolean', example: true } + expose :runner_type, + documentation: { type: 'string', values: ::Ci::Runner.runner_types.keys, example: 'instance_type' } + expose :name, documentation: { type: 'string', example: 'test' } + expose :online?, as: :online, documentation: { type: 'boolean', example: true } # DEPRECATED # TODO Remove in v5 in favor of `status` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/375709 - expose :deprecated_rest_status, as: :status + expose :deprecated_rest_status, as: :status, documentation: { type: 'string', example: 'online' } end end end diff --git a/lib/api/entities/ci/secure_file.rb b/lib/api/entities/ci/secure_file.rb index 639615e5779..d957e4488fd 100644 --- a/lib/api/entities/ci/secure_file.rb +++ b/lib/api/entities/ci/secure_file.rb @@ -9,6 +9,8 @@ module API expose :checksum expose :checksum_algorithm expose :created_at + expose :expires_at + expose :metadata end end end diff --git a/lib/api/entities/ci/variable.rb b/lib/api/entities/ci/variable.rb index f4d5248245a..47597cb77be 100644 --- a/lib/api/entities/ci/variable.rb +++ b/lib/api/entities/ci/variable.rb @@ -4,10 +4,16 @@ module API module Entities module Ci class Variable < Grape::Entity - expose :variable_type, :key, :value - expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) } - expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) } - expose :environment_scope, if: -> (entity, _) { entity.respond_to?(:environment_scope) } + expose :variable_type, documentation: { type: 'string', example: 'env_var' } + expose :key, documentation: { type: 'string', example: 'TEST_VARIABLE_1' } + expose :value, documentation: { type: 'string', example: 'TEST_1' } + expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) }, + documentation: { type: 'boolean' } + expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) }, + documentation: { type: 'boolean' } + expose :raw?, as: :raw, if: -> (entity, _) { entity.respond_to?(:raw?) }, documentation: { type: 'boolean' } + expose :environment_scope, if: -> (entity, _) { entity.respond_to?(:environment_scope) }, + documentation: { type: 'string', example: '*' } end end end diff --git a/lib/api/entities/commit.rb b/lib/api/entities/commit.rb index 6cd180cd584..ab1f51289d7 100644 --- a/lib/api/entities/commit.rb +++ b/lib/api/entities/commit.rb @@ -3,15 +3,26 @@ module API module Entities class Commit < Grape::Entity - expose :id, :short_id, :created_at - expose :parent_ids - expose :full_title, as: :title - expose :safe_message, as: :message - expose :author_name, :author_email, :authored_date - expose :committer_name, :committer_email, :committed_date - expose :trailers + expose :id, documentation: { type: 'string', example: '2695effb5807a22ff3d138d593fd856244e155e7' } + expose :short_id, documentation: { type: 'string', example: '2695effb' } + expose :created_at, documentation: { type: 'dateTime', example: '2017-07-26T11:08:53.000+02:00' } + expose :parent_ids, + documentation: { type: 'string', is_array: true, example: '2a4b78934375d7f53875269ffd4f45fd83a84ebe' } + expose :full_title, as: :title, documentation: { type: 'string', example: 'Initial commit' } + expose :safe_message, as: :message, documentation: { type: 'string', example: 'Initial commit' } + expose :author_name, documentation: { type: 'string', example: 'John Smith' } + expose :author_email, documentation: { type: 'string', example: 'john@example.com' } + expose :authored_date, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :committer_name, documentation: { type: 'string', example: 'Jack Smith' } + expose :committer_email, documentation: { type: 'string', example: 'jack@example.com' } + expose :committed_date, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :trailers, documentation: { type: 'object', example: '{ "Merged-By": "Jane Doe janedoe@gitlab.com" }' } - expose :web_url do |commit, _options| + expose :web_url, + documentation: { + type: 'string', + example: 'https://gitlab.example.com/janedoe/gitlab-foss/-/commit/ed899a2f4b50b4370feeea94676502b42383c746' + } do |commit, _options| c = commit c = c.__subject__ if c.is_a?(Gitlab::View::Presenter::Base) Gitlab::UrlBuilder.build(c) diff --git a/lib/api/entities/commit_detail.rb b/lib/api/entities/commit_detail.rb index cc529639359..428c53f7fe3 100644 --- a/lib/api/entities/commit_detail.rb +++ b/lib/api/entities/commit_detail.rb @@ -6,10 +6,10 @@ module API include ::API::Helpers::Presentable expose :stats, using: Entities::CommitStats, if: :include_stats - expose :status_for, as: :status - expose :project_id + expose :status_for, as: :status, documentation: { type: 'string', example: 'success' } + expose :project_id, documentation: { type: 'integer', example: 1 } - expose :last_pipeline do |commit, options| + expose :last_pipeline, documentation: { type: ::API::Entities::Ci::PipelineBasic.to_s } do |commit, options| pipeline = commit.last_pipeline if can_read_pipeline? ::API::Entities::Ci::PipelineBasic.represent(pipeline, options) end diff --git a/lib/api/entities/commit_note.rb b/lib/api/entities/commit_note.rb index fe91712b48d..0632dc467b8 100644 --- a/lib/api/entities/commit_note.rb +++ b/lib/api/entities/commit_note.rb @@ -3,12 +3,22 @@ module API module Entities class CommitNote < Grape::Entity - expose :note - expose(:path) { |note| note.diff_file.try(:file_path) if note.diff_note? } - expose(:line) { |note| note.diff_line.try(:line) if note.diff_note? } - expose(:line_type) { |note| note.diff_line.try(:type) if note.diff_note? } + expose :note, documentation: { type: 'string', example: 'this doc is really nice' } + + expose :path, documentation: { type: 'string', example: 'README.md' } do |note| + note.diff_file.try(:file_path) if note.diff_note? + end + + expose :line, documentation: { type: 'integer', example: 11 } do |note| + note.diff_line.try(:line) if note.diff_note? + end + + expose :line_type, documentation: { type: 'string', example: 'new' } do |note| + note.diff_line.try(:type) if note.diff_note? + end + expose :author, using: Entities::UserBasic - expose :created_at + expose :created_at, documentation: { type: 'dateTime', example: '2016-01-19T09:44:55.600Z' } end end end diff --git a/lib/api/entities/commit_signature.rb b/lib/api/entities/commit_signature.rb index 0d8e977a9f5..9430dd5e2a2 100644 --- a/lib/api/entities/commit_signature.rb +++ b/lib/api/entities/commit_signature.rb @@ -3,7 +3,7 @@ module API module Entities class CommitSignature < Grape::Entity - expose :signature_type + expose :signature_type, documentation: { type: 'string', example: 'PGP' } expose :signature, merge: true do |commit, options| if commit.signature.is_a?(::CommitSignatures::GpgSignature) || commit.raw_commit_from_rugged? @@ -13,7 +13,7 @@ module API end end - expose :commit_source do |commit, _| + expose :commit_source, documentation: { type: 'string', example: 'gitaly' } do |commit, _| commit.raw_commit_from_rugged? ? "rugged" : "gitaly" end diff --git a/lib/api/entities/commit_stats.rb b/lib/api/entities/commit_stats.rb index d9ba99c8eb0..e07483e5d97 100644 --- a/lib/api/entities/commit_stats.rb +++ b/lib/api/entities/commit_stats.rb @@ -3,7 +3,9 @@ module API module Entities class CommitStats < Grape::Entity - expose :additions, :deletions, :total + expose :additions, documentation: { type: 'integer', example: 1 } + expose :deletions, documentation: { type: 'integer', example: 0 } + expose :total, documentation: { type: 'integer', example: 1 } end end end diff --git a/lib/api/entities/commit_status.rb b/lib/api/entities/commit_status.rb index 61b8bf89cfe..df6a41895ff 100644 --- a/lib/api/entities/commit_status.rb +++ b/lib/api/entities/commit_status.rb @@ -3,8 +3,22 @@ module API module Entities class CommitStatus < Grape::Entity - expose :id, :sha, :ref, :status, :name, :target_url, :description, - :created_at, :started_at, :finished_at, :allow_failure, :coverage + expose :id, documentation: { type: 'integer', example: 93 } + expose :sha, documentation: { type: 'string', example: '18f3e63d05582537db6d183d9d557be09e1f90c8' } + expose :ref, documentation: { type: 'string', example: 'develop' } + expose :status, documentation: { type: 'string', example: 'success' } + expose :name, documentation: { type: 'string', example: 'default' } + expose :target_url, documentation: { + type: 'string', + example: 'https://gitlab.example.com/janedoe/gitlab-foss/builds/91' + } + expose :description, documentation: { type: 'string' } + expose :created_at, documentation: { type: 'dateTime', example: '2016-01-19T09:05:50.355Z' } + expose :started_at, documentation: { type: 'dateTime', example: '2016-01-20T08:40:25.832Z' } + expose :finished_at, documentation: { type: 'dateTime', example: '2016-01-21T08:40:25.832Z' } + expose :allow_failure, documentation: { type: 'boolean', example: false } + expose :coverage, documentation: { type: 'number', format: 'float', example: 98.29 } + expose :author, using: Entities::UserBasic end end diff --git a/lib/api/entities/compare.rb b/lib/api/entities/compare.rb index 75a36d9bb01..92066868d3c 100644 --- a/lib/api/entities/compare.rb +++ b/lib/api/entities/compare.rb @@ -7,21 +7,24 @@ module API compare.commits.last end - expose :commits, using: Entities::Commit do |compare, _| + expose :commits, documentation: { is_array: true }, using: Entities::Commit do |compare, _| compare.commits end - expose :diffs, using: Entities::Diff do |compare, _| + expose :diffs, documentation: { is_array: true }, using: Entities::Diff do |compare, _| compare.diffs.diffs.to_a end - expose :compare_timeout do |compare, _| + expose :compare_timeout, documentation: { type: 'boolean' } do |compare, _| compare.diffs.diffs.overflow? end - expose :same, as: :compare_same_ref + expose :same, as: :compare_same_ref, documentation: { type: 'boolean' } - expose :web_url do |compare, _| + expose :web_url, + documentation: { + example: "https://gitlab.example.com/gitlab/gitlab-foss/-/compare/main...feature" + } do |compare, _| Gitlab::UrlBuilder.build(compare) end end diff --git a/lib/api/entities/container_registry.rb b/lib/api/entities/container_registry.rb index 2fdfac40c32..d12c8142e69 100644 --- a/lib/api/entities/container_registry.rb +++ b/lib/api/entities/container_registry.rb @@ -12,13 +12,13 @@ module API class Repository < Grape::Entity include ::API::Helpers::RelatedResourcesHelpers - expose :id - expose :name - expose :path - expose :project_id - expose :location - expose :created_at - expose :expiration_policy_started_at, as: :cleanup_policy_started_at + expose :id, documentation: { type: 'integer', example: 1 } + expose :name, documentation: { type: 'string', example: 'releases' } + expose :path, documentation: { type: 'string', example: 'group/project/releases' } + expose :project_id, documentation: { type: 'integer', example: 9 } + expose :location, documentation: { type: 'string', example: 'gitlab.example.com/group/project/releases' } + expose :created_at, documentation: { type: 'dateTime', example: '2019-01-10T13:39:08.229Z' } + expose :expiration_policy_started_at, as: :cleanup_policy_started_at, documentation: { type: 'dateTime', example: '2020-08-17T03:12:35.489Z' } expose :tags_count, if: -> (_, options) { options[:tags_count] } expose :tags, using: Tag, if: -> (_, options) { options[:tags] } expose :delete_api_path, if: ->(object, options) { Ability.allowed?(options[:user], :admin_container_image, object) } diff --git a/lib/api/entities/contributor.rb b/lib/api/entities/contributor.rb index 8763822b674..4fab953f0f6 100644 --- a/lib/api/entities/contributor.rb +++ b/lib/api/entities/contributor.rb @@ -3,7 +3,11 @@ module API module Entities class Contributor < Grape::Entity - expose :name, :email, :commits, :additions, :deletions + expose :name, documentation: { example: 'John Doe' } + expose :email, documentation: { example: 'johndoe@example.com' } + expose :commits, documentation: { type: 'integer', example: 117 } + expose :additions, documentation: { type: 'integer', example: 3 } + expose :deletions, documentation: { type: 'integer', example: 5 } end end end diff --git a/lib/api/entities/custom_attribute.rb b/lib/api/entities/custom_attribute.rb index f949b709517..883b572ac75 100644 --- a/lib/api/entities/custom_attribute.rb +++ b/lib/api/entities/custom_attribute.rb @@ -3,8 +3,8 @@ module API module Entities class CustomAttribute < Grape::Entity - expose :key - expose :value + expose :key, documentation: { type: 'string', example: 'foo' } + expose :value, documentation: { type: 'string', example: 'bar' } end end end diff --git a/lib/api/entities/deploy_key.rb b/lib/api/entities/deploy_key.rb index 2c9c33549a1..1bcd06f2c88 100644 --- a/lib/api/entities/deploy_key.rb +++ b/lib/api/entities/deploy_key.rb @@ -3,9 +3,15 @@ module API module Entities class DeployKey < Entities::SSHKey - expose :key - expose :fingerprint, if: ->(key, _) { key.fingerprint.present? } - expose :fingerprint_sha256 + expose :key, + documentation: { type: 'string', example: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNJAkI3Wdf0r13c8a5pEExB2YowPWCSVzfZV22pNBc1CuEbyYLHpUyaD0GwpGvFdx2aP7lMEk35k6Rz3ccBF6jRaVJyhsn5VNnW92PMpBJ/P1UebhXwsFHdQf5rTt082cSxWuk61kGWRQtk4ozt/J2DF/dIUVaLvc+z4HomT41fQ==' } + + expose :fingerprint, + documentation: { type: 'string', example: '4a:9d:64:15:ed:3a:e6:07:6e:89:36:b3:3b:03:05:d9' }, + if: ->(key, _) { key.fingerprint.present? } + + expose :fingerprint_sha256, + documentation: { type: 'string', example: 'SHA256:Jrs3LD1Ji30xNLtTVf9NDCj7kkBgPBb2pjvTZ3HfIgU' } expose :projects_with_write_access, using: Entities::ProjectIdentity, if: -> (_, options) { options[:include_projects_with_write_access] } end diff --git a/lib/api/entities/deploy_keys_project.rb b/lib/api/entities/deploy_keys_project.rb index 12a86fbdf8e..4501af88067 100644 --- a/lib/api/entities/deploy_keys_project.rb +++ b/lib/api/entities/deploy_keys_project.rb @@ -4,7 +4,7 @@ module API module Entities class DeployKeysProject < Grape::Entity expose :deploy_key, merge: true, using: Entities::DeployKey - expose :can_push + expose :can_push, documentation: { type: 'boolean' } end end end diff --git a/lib/api/entities/deploy_token.rb b/lib/api/entities/deploy_token.rb index daee104ba6b..9861467e35d 100644 --- a/lib/api/entities/deploy_token.rb +++ b/lib/api/entities/deploy_token.rb @@ -4,8 +4,13 @@ module API module Entities class DeployToken < Grape::Entity # exposing :token is a security risk and should be avoided - expose :id, :name, :username, :expires_at, :scopes, :revoked - expose :expired?, as: :expired + expose :id, documentation: { type: 'integer', example: 1 } + expose :name, documentation: { type: 'string', example: 'MyToken' } + expose :username, documentation: { type: 'string', example: 'gitlab+deploy-token-1' } + expose :expires_at, documentation: { type: 'dateTime', example: '2020-02-14T00:00:00.000Z' } + expose :scopes, documentation: { type: 'array', example: ['read_repository'] } + expose :revoked, documentation: { type: 'boolean' } + expose :expired?, documentation: { type: 'boolean' }, as: :expired end end end diff --git a/lib/api/entities/deploy_token_with_token.rb b/lib/api/entities/deploy_token_with_token.rb index 11efe3720fa..a96051e1403 100644 --- a/lib/api/entities/deploy_token_with_token.rb +++ b/lib/api/entities/deploy_token_with_token.rb @@ -3,7 +3,7 @@ module API module Entities class DeployTokenWithToken < Entities::DeployToken - expose :token + expose :token, documentation: { type: 'string', example: 'jMRvtPNxrn3crTAGukpZ' } end end end diff --git a/lib/api/entities/deployment.rb b/lib/api/entities/deployment.rb index 4e3a4c289d9..426e92e7723 100644 --- a/lib/api/entities/deployment.rb +++ b/lib/api/entities/deployment.rb @@ -3,11 +3,16 @@ module API module Entities class Deployment < Grape::Entity - expose :id, :iid, :ref, :sha, :created_at, :updated_at + expose :id, documentation: { type: 'integer', example: 41 } + expose :iid, documentation: { type: 'integer', example: 1 } + expose :ref, documentation: { type: 'string', example: 'main' } + expose :sha, documentation: { type: 'string', example: '99d03678b90d914dbb1b109132516d71a4a03ea8' } + expose :created_at, documentation: { type: 'dateTime', example: '2016-08-11T11:32:35.444Z' } + expose :updated_at, documentation: { type: 'dateTime', example: '2016-08-11T11:32:35.444Z' } expose :user, using: Entities::UserBasic expose :environment, using: Entities::EnvironmentBasic expose :deployable, using: Entities::Ci::Job - expose :status + expose :status, documentation: { type: 'string', example: 'created' } end end end diff --git a/lib/api/entities/diff.rb b/lib/api/entities/diff.rb index e92bc5d6b68..e9650f07f00 100644 --- a/lib/api/entities/diff.rb +++ b/lib/api/entities/diff.rb @@ -3,11 +3,17 @@ module API module Entities class Diff < Grape::Entity - expose :old_path, :new_path, :a_mode, :b_mode - expose :new_file?, as: :new_file - expose :renamed_file?, as: :renamed_file - expose :deleted_file?, as: :deleted_file - expose :json_safe_diff, as: :diff + expose :json_safe_diff, as: :diff, documentation: { + type: 'string', + example: '--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n...' + } + expose :new_path, documentation: { type: 'string', example: 'doc/update/5.4-to-6.0.md' } + expose :old_path, documentation: { type: 'string', example: 'doc/update/5.4-to-6.0.md' } + expose :a_mode, documentation: { type: 'string', example: '100755' } + expose :b_mode, documentation: { type: 'string', example: '100644' } + expose :new_file?, as: :new_file, documentation: { type: 'boolean' } + expose :renamed_file?, as: :renamed_file, documentation: { type: 'boolean' } + expose :deleted_file?, as: :deleted_file, documentation: { type: 'boolean' } end end end diff --git a/lib/api/entities/entity_helpers.rb b/lib/api/entities/entity_helpers.rb index 3a68044ad35..6fb04bb8ad6 100644 --- a/lib/api/entities/entity_helpers.rb +++ b/lib/api/entities/entity_helpers.rb @@ -11,8 +11,8 @@ module API ->(obj, opts) { Ability.allowed?(opts[:user], "destroy_#{attr}".to_sym, yield(obj)) } end - def expose_restricted(attr, &block) - expose attr, if: can_read(attr, &block) + def expose_restricted(attr, documentation: {}, &block) + expose attr, documentation: documentation, if: can_read(attr, &block) end end end diff --git a/lib/api/entities/environment.rb b/lib/api/entities/environment.rb index 3b6ed94c3f1..dc9911d5acb 100644 --- a/lib/api/entities/environment.rb +++ b/lib/api/entities/environment.rb @@ -5,10 +5,10 @@ module API class Environment < Entities::EnvironmentBasic include RequestAwareEntity - expose :tier + expose :tier, documentation: { type: 'string', example: 'development' } expose :project, using: Entities::BasicProjectDetails expose :last_deployment, using: Entities::Deployment, if: { last_deployment: true } - expose :state + expose :state, documentation: { type: 'string', example: 'available' } end end end diff --git a/lib/api/entities/environment_basic.rb b/lib/api/entities/environment_basic.rb index d9894eac147..1b4a9371820 100644 --- a/lib/api/entities/environment_basic.rb +++ b/lib/api/entities/environment_basic.rb @@ -3,7 +3,12 @@ module API module Entities class EnvironmentBasic < Grape::Entity - expose :id, :name, :slug, :external_url, :created_at, :updated_at + expose :id, documentation: { type: 'integer', example: 1 } + expose :name, documentation: { type: 'string', example: 'deploy' } + expose :slug, documentation: { type: 'string', example: 'deploy' } + expose :external_url, documentation: { type: 'string', example: 'https://deploy.gitlab.example.com' } + expose :created_at, documentation: { type: 'dateTime', example: '2019-05-25T18:55:13.252Z' } + expose :updated_at, documentation: { type: 'dateTime', example: '2019-05-25T18:55:13.252Z' } end end end diff --git a/lib/api/entities/error_tracking.rb b/lib/api/entities/error_tracking.rb index 163bda92680..5e3b983c58c 100644 --- a/lib/api/entities/error_tracking.rb +++ b/lib/api/entities/error_tracking.rb @@ -4,11 +4,11 @@ module API module Entities module ErrorTracking class ProjectSetting < Grape::Entity - expose :enabled, as: :active - expose :project_name - expose :sentry_external_url - expose :api_url - expose :integrated + expose :enabled, as: :active, documentation: { type: 'boolean' } + expose :project_name, documentation: { type: 'string', example: 'sample sentry project' } + expose :sentry_external_url, documentation: { type: 'string', example: 'https://sentry.io/myawesomeproject/project' } + expose :api_url, documentation: { type: 'string', example: 'https://sentry.io/api/0/projects/myawesomeproject/project' } + expose :integrated, documentation: { type: 'boolean' } def integrated return false unless ::Feature.enabled?(:integrated_error_tracking, object.project) @@ -18,10 +18,10 @@ module API end class ClientKey < Grape::Entity - expose :id - expose :active - expose :public_key - expose :sentry_dsn + expose :id, documentation: { type: 'integer', example: 1 } + expose :active, documentation: { type: 'boolean' } + expose :public_key, documentation: { type: 'string', example: 'glet_aa77551d849c083f76d0bc545ed053a3' } + expose :sentry_dsn, documentation: { type: 'string', example: 'https://glet_aa77551d849c083f76d0bc545ed053a3@gitlab.example.com/api/v4/error_tracking/collector/5' } end end end diff --git a/lib/api/entities/feature.rb b/lib/api/entities/feature.rb index d1151849cd7..48dd5a22a7e 100644 --- a/lib/api/entities/feature.rb +++ b/lib/api/entities/feature.rb @@ -3,8 +3,8 @@ module API module Entities class Feature < Grape::Entity - expose :name - expose :state + expose :name, documentation: { type: 'string', example: 'experimental_feature' } + expose :state, documentation: { type: 'string', example: 'off' } expose :gates, using: Entities::FeatureGate do |model| model.gates.map do |gate| value = model.gate_values[gate.key] diff --git a/lib/api/entities/feature_flag.rb b/lib/api/entities/feature_flag.rb index 9dec3873504..273307357a2 100644 --- a/lib/api/entities/feature_flag.rb +++ b/lib/api/entities/feature_flag.rb @@ -3,12 +3,12 @@ module API module Entities class FeatureFlag < Grape::Entity - expose :name - expose :description - expose :active - expose :version - expose :created_at - expose :updated_at + expose :name, documentation: { type: 'string', example: 'merge_train' } + expose :description, documentation: { type: 'string', example: 'merge train feature flag' } + expose :active, documentation: { type: 'boolean' } + expose :version, documentation: { type: 'string', example: 'new_version_flag' } + expose :created_at, documentation: { type: 'dateTime', example: '2019-11-04T08:13:51.423Z' } + expose :updated_at, documentation: { type: 'dateTime', example: '2019-11-04T08:13:51.423Z' } expose :scopes do |_ff| [] end diff --git a/lib/api/entities/feature_flag/scope.rb b/lib/api/entities/feature_flag/scope.rb index 906fe718257..e29793c250a 100644 --- a/lib/api/entities/feature_flag/scope.rb +++ b/lib/api/entities/feature_flag/scope.rb @@ -4,8 +4,8 @@ module API module Entities class FeatureFlag < Grape::Entity class Scope < Grape::Entity - expose :id - expose :environment_scope + expose :id, documentation: { type: 'integer', example: 1 } + expose :environment_scope, documentation: { type: 'string', example: 'production' } end end end diff --git a/lib/api/entities/feature_flag/strategy.rb b/lib/api/entities/feature_flag/strategy.rb index 32699be0ee3..62178420370 100644 --- a/lib/api/entities/feature_flag/strategy.rb +++ b/lib/api/entities/feature_flag/strategy.rb @@ -4,9 +4,9 @@ module API module Entities class FeatureFlag < Grape::Entity class Strategy < Grape::Entity - expose :id - expose :name - expose :parameters + expose :id, documentation: { type: 'integer', example: 1 } + expose :name, documentation: { type: 'string', example: 'userWithId' } + expose :parameters, documentation: { type: 'string', example: '{"userIds": "user1"}' } expose :scopes, using: FeatureFlag::Scope end end diff --git a/lib/api/entities/feature_flag/user_list.rb b/lib/api/entities/feature_flag/user_list.rb index bc8b12ea22e..efb3261658a 100644 --- a/lib/api/entities/feature_flag/user_list.rb +++ b/lib/api/entities/feature_flag/user_list.rb @@ -6,13 +6,13 @@ module API class UserList < Grape::Entity include RequestAwareEntity - expose :id - expose :iid - expose :project_id - expose :created_at - expose :updated_at - expose :name - expose :user_xids + expose :id, documentation: { type: 'integer', example: 1 } + expose :iid, documentation: { type: 'integer', example: 1 } + expose :project_id, documentation: { type: 'integer', example: 2 } + expose :created_at, documentation: { type: 'dateTime', example: '2020-02-04T08:13:10.507Z' } + expose :updated_at, documentation: { type: 'dateTime', example: '2020-02-04T08:13:10.507Z' } + expose :name, documentation: { type: 'string', example: 'user_list' } + expose :user_xids, documentation: { type: 'string', example: 'user1,user2' } expose :path do |list| project_feature_flags_user_list_path(list.project, list) diff --git a/lib/api/entities/feature_gate.rb b/lib/api/entities/feature_gate.rb index bea9c9474b3..ad4702bf210 100644 --- a/lib/api/entities/feature_gate.rb +++ b/lib/api/entities/feature_gate.rb @@ -3,8 +3,8 @@ module API module Entities class FeatureGate < Grape::Entity - expose :key - expose :value + expose :key, documentation: { type: 'string', example: 'percentage_of_actors' } + expose :value, documentation: { type: 'integer', example: 34 } end end end diff --git a/lib/api/entities/freeze_period.rb b/lib/api/entities/freeze_period.rb index 9b5f08925db..d6853c544a5 100644 --- a/lib/api/entities/freeze_period.rb +++ b/lib/api/entities/freeze_period.rb @@ -3,9 +3,11 @@ module API module Entities class FreezePeriod < Grape::Entity - expose :id - expose :freeze_start, :freeze_end, :cron_timezone - expose :created_at, :updated_at + expose :id, documentation: { type: 'integer', example: 1 } + expose :freeze_start, documentation: { type: 'string', example: '0 23 * * 5' } + expose :freeze_end, documentation: { type: 'string', example: '0 8 * * 1' } + expose :cron_timezone, documentation: { type: 'string', example: 'UTC' } + expose :created_at, :updated_at, documentation: { type: 'dateTime', example: '2020-05-15T17:03:35.702Z' } end end end diff --git a/lib/api/entities/go_module_version.rb b/lib/api/entities/go_module_version.rb index 643e25df9e0..b9dd88982dd 100644 --- a/lib/api/entities/go_module_version.rb +++ b/lib/api/entities/go_module_version.rb @@ -3,8 +3,8 @@ module API module Entities class GoModuleVersion < Grape::Entity - expose :name, as: 'Version' - expose :time, as: 'Time' + expose :name, as: 'Version', documentation: { type: 'string', example: 'v1.0.0' } + expose :time, as: 'Time', documentation: { type: 'string', example: '1617822312 -0600' } end end end diff --git a/lib/api/entities/hook.rb b/lib/api/entities/hook.rb index 95924321221..e24e201ac57 100644 --- a/lib/api/entities/hook.rb +++ b/lib/api/entities/hook.rb @@ -3,12 +3,18 @@ module API module Entities class Hook < Grape::Entity - expose :id, :url, :created_at, :push_events, :tag_push_events, :merge_requests_events, :repository_update_events - expose :enable_ssl_verification + expose :id, documentation: { type: 'string', example: 1 } + expose :url, documentation: { type: 'string', example: 'https://webhook.site' } + expose :created_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :push_events, documentation: { type: 'boolean' } + expose :tag_push_events, documentation: { type: 'boolean' } + expose :merge_requests_events, documentation: { type: 'boolean' } + expose :repository_update_events, documentation: { type: 'boolean' } + expose :enable_ssl_verification, documentation: { type: 'boolean' } - expose :alert_status - expose :disabled_until - expose :url_variables + expose :alert_status, documentation: { type: 'symbol', example: :executable } + expose :disabled_until, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :url_variables, documentation: { type: 'Hash', example: { "token" => "secr3t" }, is_array: true } def url_variables object.url_variables.keys.map { { key: _1 } } diff --git a/lib/api/entities/issuable_entity.rb b/lib/api/entities/issuable_entity.rb index e2c674c0b8b..4e70f945a48 100644 --- a/lib/api/entities/issuable_entity.rb +++ b/lib/api/entities/issuable_entity.rb @@ -3,10 +3,16 @@ module API module Entities class IssuableEntity < Grape::Entity - expose :id, :iid - expose(:project_id) { |entity| entity&.project.try(:id) } - expose :title, :description - expose :state, :created_at, :updated_at + expose :id, documentation: { type: 'integer', example: 84 } + expose :iid, documentation: { type: 'integer', example: 14 } + expose :project_id, documentation: { type: 'integer', example: 4 } do |entity| + entity&.project.try(:id) + end + expose :title, documentation: { type: 'string', example: 'Impedit et ut et dolores vero provident ullam est' } + expose :description, documentation: { type: 'string', example: 'Repellendus impedit et vel velit dignissimos.' } + expose :state, documentation: { type: 'string', example: 'closed' } + expose :created_at, documentation: { type: 'dateTime', example: '2022-08-17T12:46:35.053Z' } + expose :updated_at, documentation: { type: 'dateTime', example: '2022-11-14T17:22:01.470Z' } def presented lazy_issuable_metadata diff --git a/lib/api/entities/issue_basic.rb b/lib/api/entities/issue_basic.rb index 20f66c026e6..89fb8bbe1c0 100644 --- a/lib/api/entities/issue_basic.rb +++ b/lib/api/entities/issue_basic.rb @@ -7,10 +7,10 @@ module API item.upcase if item.respond_to?(:upcase) end - expose :closed_at + expose :closed_at, documentation: { type: 'dateTime', example: '2022-11-15T08:30:55.232Z' } expose :closed_by, using: Entities::UserBasic - expose :labels do |issue, options| + expose :labels, documentation: { type: 'string', is_array: true, example: 'bug' } do |issue, options| if options[:with_labels_details] ::API::Entities::LabelBasic.represent(issue.labels.sort_by(&:title)) else @@ -23,7 +23,7 @@ module API expose :issue_type, as: :type, format_with: :upcase, - documentation: { type: "String", desc: "One of #{::WorkItems::Type.allowed_types_for_issues.map(&:upcase)}" } + documentation: { type: 'String', example: 'ISSUE', desc: "One of #{::WorkItems::Type.allowed_types_for_issues.map(&:upcase)}" } expose :assignee, using: ::API::Entities::UserBasic do |issue| issue.assignees.first @@ -33,12 +33,12 @@ module API expose(:merge_requests_count) { |issue, options| issuable_metadata.merge_requests_count } expose(:upvotes) { |issue, options| issuable_metadata.upvotes } expose(:downvotes) { |issue, options| issuable_metadata.downvotes } - expose :due_date - expose :confidential - expose :discussion_locked - expose :issue_type + expose :due_date, documentation: { type: 'date', example: '2022-11-20' } + expose :confidential, documentation: { type: 'boolean' } + expose :discussion_locked, documentation: { type: 'boolean' } + expose :issue_type, documentation: { type: 'string', example: 'issue' } - expose :web_url do |issue| + expose :web_url, documentation: { type: 'string', example: 'http://example.com/example/example/issues/14' } do |issue| Gitlab::UrlBuilder.build(issue) end diff --git a/lib/api/entities/license.rb b/lib/api/entities/license.rb index 8ecf8a430fe..6318fec6774 100644 --- a/lib/api/entities/license.rb +++ b/lib/api/entities/license.rb @@ -4,12 +4,25 @@ module API module Entities # Serializes a Licensee::License class License < Entities::LicenseBasic - expose :popular?, as: :popular - expose(:description) { |license| license.meta['description'] } - expose(:conditions) { |license| license.meta['conditions'] } - expose(:permissions) { |license| license.meta['permissions'] } - expose(:limitations) { |license| license.meta['limitations'] } - expose :content + expose :popular?, as: :popular, documentation: { type: 'boolean' } + + expose :description, documentation: { type: 'string', example: 'A simple license' } do |license| + license.meta['description'] + end + + expose :conditions, documentation: { type: 'string', is_array: true, example: 'include-copyright' } do |license| + license.meta['conditions'] + end + + expose :permissions, documentation: { type: 'string', is_array: true, example: 'commercial-use' } do |license| + license.meta['permissions'] + end + + expose :limitations, documentation: { type: 'string', is_array: true, example: 'liability' } do |license| + license.meta['limitations'] + end + + expose :content, documentation: { type: 'string', example: 'GNU GENERAL PUBLIC LICENSE' } end end end diff --git a/lib/api/entities/license_basic.rb b/lib/api/entities/license_basic.rb index 0916738d21d..e3bb55d4104 100644 --- a/lib/api/entities/license_basic.rb +++ b/lib/api/entities/license_basic.rb @@ -4,8 +4,10 @@ module API module Entities # Serializes a Gitlab::Git::DeclaredLicense class LicenseBasic < Grape::Entity - expose :key, :name, :nickname - expose :url, as: :html_url + expose :key, documentation: { type: 'string', example: 'gpl-3.0' } + expose :name, documentation: { type: 'string', example: 'GNU General Public License v3.0' } + expose :nickname, documentation: { type: 'string', example: 'GNU GPLv3' } + expose :url, as: :html_url, documentation: { example: 'http://choosealicense.com/licenses/gpl-3.0' } # This was dropped: # https://github.com/github/choosealicense.com/commit/325806b42aa3d5b78e84120327ec877bc936dbdd#diff-66df8f1997786f7052d29010f2cbb4c66391d60d24ca624c356acc0ab986f139 diff --git a/lib/api/entities/markdown.rb b/lib/api/entities/markdown.rb new file mode 100644 index 00000000000..0fbaec4375e --- /dev/null +++ b/lib/api/entities/markdown.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module API + module Entities + class Markdown < Grape::Entity + expose :html, documentation: { type: 'string', example: '

Hello world!

"' } + end + end +end diff --git a/lib/api/entities/merge_request_approvals.rb b/lib/api/entities/merge_request_approvals.rb index 6810952b2fc..54f196e0d74 100644 --- a/lib/api/entities/merge_request_approvals.rb +++ b/lib/api/entities/merge_request_approvals.rb @@ -3,15 +3,15 @@ module API module Entities class MergeRequestApprovals < Grape::Entity - expose :user_has_approved do |merge_request, options| + expose :user_has_approved, documentation: { type: 'boolean' } do |merge_request, options| merge_request.approved_by?(options[:current_user]) end - expose :user_can_approve do |merge_request, options| + expose :user_can_approve, documentation: { type: 'boolean' } do |merge_request, options| merge_request.eligible_for_approval_by?(options[:current_user]) end - expose :approved do |merge_request| + expose :approved, documentation: { type: 'boolean' } do |merge_request| merge_request.approvals.present? end diff --git a/lib/api/entities/merge_request_basic.rb b/lib/api/entities/merge_request_basic.rb index 55d58166590..27f6e6ade06 100644 --- a/lib/api/entities/merge_request_basic.rb +++ b/lib/api/entities/merge_request_basic.rb @@ -58,6 +58,7 @@ module API merge_request.check_mergeability(async: true) unless options[:skip_merge_status_recheck] merge_request.public_merge_status end + expose :detailed_merge_status expose :diff_head_sha, as: :sha expose :merge_commit_sha expose :squash_commit_sha @@ -93,6 +94,12 @@ module API expose :task_completion_status expose :cannot_be_merged?, as: :has_conflicts expose :mergeable_discussions_state?, as: :blocking_discussions_resolved + + private + + def detailed_merge_status + ::MergeRequests::Mergeability::DetailedMergeStatusService.new(merge_request: object).execute + end end end end diff --git a/lib/api/entities/merge_request_simple.rb b/lib/api/entities/merge_request_simple.rb index f3ff4cc18a8..d5c511ad9a4 100644 --- a/lib/api/entities/merge_request_simple.rb +++ b/lib/api/entities/merge_request_simple.rb @@ -3,8 +3,11 @@ module API module Entities class MergeRequestSimple < IssuableEntity - expose :title - expose :web_url do |merge_request, options| + expose :title, documentation: { type: 'string', example: 'Test MR 1580978354' } + expose :web_url, + documentation: { + type: 'string', example: 'http://local.gitlab.test:8181/root/merge-train-race-condition/-/merge_requests/59' + } do |merge_request, options| Gitlab::UrlBuilder.build(merge_request) end end diff --git a/lib/api/entities/metadata.rb b/lib/api/entities/metadata.rb index daa491ec42a..7dfcad2ccab 100644 --- a/lib/api/entities/metadata.rb +++ b/lib/api/entities/metadata.rb @@ -3,13 +3,14 @@ module API module Entities class Metadata < Grape::Entity - expose :version - expose :revision + expose :version, documentation: { type: 'string', example: '15.2-pre' } + expose :revision, documentation: { type: 'string', example: 'c401a659d0c' } expose :kas do expose :enabled, documentation: { type: 'boolean' } - expose :externalUrl - expose :version + expose :externalUrl, documentation: { type: 'string', example: 'grpc://gitlab.example.com:8150' } + expose :version, documentation: { type: 'string', example: '15.0.0' } end + expose :enterprise, documentation: { type: 'boolean' } end end end diff --git a/lib/api/entities/metrics/dashboard/annotation.rb b/lib/api/entities/metrics/dashboard/annotation.rb index 66bd09d84f9..08d1a333259 100644 --- a/lib/api/entities/metrics/dashboard/annotation.rb +++ b/lib/api/entities/metrics/dashboard/annotation.rb @@ -5,13 +5,13 @@ module API module Metrics module Dashboard class Annotation < Grape::Entity - expose :id - expose :starting_at - expose :ending_at - expose :dashboard_path - expose :description - expose :environment_id - expose :cluster_id + expose :id, documentation: { type: 'integer', example: 4 } + expose :starting_at, documentation: { type: 'dateTime', example: '2016-04-08T03:45:40.000Z' } + expose :ending_at, documentation: { type: 'dateTime', example: '2016-08-08T09:00:00.000Z' } + expose :dashboard_path, documentation: { type: 'string', example: '.gitlab/dashboards/custom_metrics.yml' } + expose :description, documentation: { type: 'string', example: 'annotation description' } + expose :environment_id, documentation: { type: 'integer', example: 1 } + expose :cluster_id, documentation: { type: 'integer', example: 2 } end end end diff --git a/lib/api/entities/metrics/user_starred_dashboard.rb b/lib/api/entities/metrics/user_starred_dashboard.rb index d774160e3ea..1d2a8a39547 100644 --- a/lib/api/entities/metrics/user_starred_dashboard.rb +++ b/lib/api/entities/metrics/user_starred_dashboard.rb @@ -4,7 +4,10 @@ module API module Entities module Metrics class UserStarredDashboard < Grape::Entity - expose :id, :dashboard_path, :user_id, :project_id + expose :id, documentation: { type: 'integer', example: 5 } + expose :dashboard_path, documentation: { type: 'string', example: 'config/prometheus/common_metrics.yml' } + expose :user_id, documentation: { type: 'integer', example: 1 } + expose :project_id, documentation: { type: 'integer', example: 20 } end end end diff --git a/lib/api/entities/ml/mlflow/run.rb b/lib/api/entities/ml/mlflow/run.rb index a8e1cfe08dd..8b16c67611f 100644 --- a/lib/api/entities/ml/mlflow/run.rb +++ b/lib/api/entities/ml/mlflow/run.rb @@ -6,7 +6,7 @@ module API module Mlflow class Run < Grape::Entity expose :run do - expose(:info) { |candidate| RunInfo.represent(candidate) } + expose :itself, using: RunInfo, as: :info expose :data do expose :metrics, using: Metric expose :params, using: RunParam diff --git a/lib/api/entities/ml/mlflow/run_info.rb b/lib/api/entities/ml/mlflow/run_info.rb index 096950e349d..d3934545ba4 100644 --- a/lib/api/entities/ml/mlflow/run_info.rb +++ b/lib/api/entities/ml/mlflow/run_info.rb @@ -11,7 +11,7 @@ module API expose(:start_time) { |candidate| candidate.start_time || 0 } expose :end_time, expose_nil: false expose(:status) { |candidate| candidate.status.to_s.upcase } - expose(:artifact_uri) { |candidate| 'not_implemented' } + expose(:artifact_uri) { |candidate, options| "#{options[:packages_url]}#{candidate.artifact_root}" } expose(:lifecycle_stage) { |candidate| 'active' } expose(:user_id) { |candidate| candidate.user_id.to_s } diff --git a/lib/api/entities/ml/mlflow/update_run.rb b/lib/api/entities/ml/mlflow/update_run.rb index 090d69b8895..55def810ef5 100644 --- a/lib/api/entities/ml/mlflow/update_run.rb +++ b/lib/api/entities/ml/mlflow/update_run.rb @@ -5,13 +5,7 @@ module API module Ml module Mlflow class UpdateRun < Grape::Entity - expose :run_info - - private - - def run_info - RunInfo.represent object - end + expose :itself, using: RunInfo, as: :run_info end end end diff --git a/lib/api/entities/package.rb b/lib/api/entities/package.rb index 18fc0576dd4..c92a4677220 100644 --- a/lib/api/entities/package.rb +++ b/lib/api/entities/package.rb @@ -4,11 +4,12 @@ module API module Entities class Package < Grape::Entity include ::API::Helpers::RelatedResourcesHelpers + include ::Routing::PackagesHelper extend ::API::Entities::EntityHelpers - expose :id + expose :id, documentation: { type: 'integer', example: 1 } - expose :name do |package| + expose :name, documentation: { type: 'string', example: '@foo/bar' } do |package| if package.conan? package.conan_recipe else @@ -20,17 +21,13 @@ module API package.name end - expose :version - expose :package_type - expose :status + expose :version, documentation: { type: 'string', example: '1.0.3' } + expose :package_type, documentation: { type: 'string', example: 'npm' } + expose :status, documentation: { type: 'string', example: 'default' } expose :_links do - expose :web_path do |package, opts| - if package.infrastructure_package? - ::Gitlab::Routing.url_helpers.namespace_project_infrastructure_registry_path(opts[:namespace], package.project, package) - else - ::Gitlab::Routing.url_helpers.project_package_path(package.project, package) - end + expose :web_path do |package| + package_path(package) end expose :delete_api_path, if: can_destroy(:package, &:project) do |package| @@ -38,10 +35,12 @@ module API end end - expose :created_at - expose :last_downloaded_at - expose :project_id, if: ->(_, opts) { opts[:group] } - expose :project_path, if: ->(obj, opts) { opts[:group] && Ability.allowed?(opts[:user], :read_project, obj.project) } + expose :created_at, documentation: { type: 'dateTime', example: '2022-09-16T12:47:31.949Z' } + expose :last_downloaded_at, documentation: { type: 'dateTime', example: '2022-09-19T11:32:35.169Z' } + expose :project_id, documentation: { type: 'integer', example: 2 }, if: ->(_, opts) { opts[:group] } + expose :project_path, documentation: { type: 'string', example: 'gitlab/foo/bar' }, if: ->(obj, opts) do + opts[:group] && Ability.allowed?(opts[:user], :read_project, obj.project) + end expose :tags expose :pipeline, if: ->(package) { package.original_build_info }, using: Package::Pipeline diff --git a/lib/api/entities/package_file.rb b/lib/api/entities/package_file.rb index e34a6a7aa1d..19372b75012 100644 --- a/lib/api/entities/package_file.rb +++ b/lib/api/entities/package_file.rb @@ -3,9 +3,14 @@ module API module Entities class PackageFile < Grape::Entity - expose :id, :package_id, :created_at - expose :file_name, :size - expose :file_md5, :file_sha1, :file_sha256 + expose :id, documentation: { type: 'integer', example: 225 } + expose :package_id, documentation: { type: 'integer', example: 4 } + expose :created_at, documentation: { type: 'dateTime', example: '2018-11-07T15:25:52.199Z' } + expose :file_name, documentation: { type: 'string', example: 'my-app-1.5-20181107.152550-1.jar' } + expose :size, documentation: { type: 'integer', example: '2421' } + expose :file_md5, documentation: { type: 'string', example: '58e6a45a629910c6ff99145a688971ac' } + expose :file_sha1, documentation: { type: 'string', example: 'ebd193463d3915d7e22219f52740056dfd26cbfe' } + expose :file_sha256, documentation: { type: 'string', example: 'a903393463d3915d7e22219f52740056dfd26cbfeff321b' } expose :pipelines, if: ->(package_file) { package_file.pipelines.present? }, using: Package::Pipeline end end diff --git a/lib/api/entities/personal_access_token.rb b/lib/api/entities/personal_access_token.rb index 55764daef9d..3ec91ca5fc9 100644 --- a/lib/api/entities/personal_access_token.rb +++ b/lib/api/entities/personal_access_token.rb @@ -3,9 +3,16 @@ module API module Entities class PersonalAccessToken < Grape::Entity - expose :id, :name, :revoked, :created_at, :scopes, :user_id, :last_used_at - expose :active?, as: :active - expose :expires_at do |personal_access_token| + expose :id, documentation: { type: 'integer', example: 2 } + expose :name, documentation: { type: 'string', example: 'John Doe' } + expose :revoked, documentation: { type: 'boolean' } + expose :created_at, documentation: { type: 'dateTime' } + expose :scopes, documentation: { type: 'array', example: ['api'] } + expose :user_id, documentation: { type: 'integer', example: 3 } + expose :last_used_at, documentation: { type: 'dateTime', example: '2020-08-31T15:53:00.073Z' } + expose :active?, as: :active, documentation: { type: 'boolean' } + expose :expires_at, documentation: + { type: 'dateTime', example: '2020-08-31T15:53:00.073Z' } do |personal_access_token| personal_access_token.expires_at ? personal_access_token.expires_at.strftime("%Y-%m-%d") : nil end end diff --git a/lib/api/entities/plan_limit.rb b/lib/api/entities/plan_limit.rb index 94e50f19b35..34018f03eb1 100644 --- a/lib/api/entities/plan_limit.rb +++ b/lib/api/entities/plan_limit.rb @@ -3,23 +3,23 @@ module API module Entities class PlanLimit < Grape::Entity - expose :ci_pipeline_size - expose :ci_active_jobs - expose :ci_active_pipelines - expose :ci_project_subscriptions - expose :ci_pipeline_schedules - expose :ci_needs_size_limit - expose :ci_registered_group_runners - expose :ci_registered_project_runners - expose :conan_max_file_size - expose :generic_packages_max_file_size - expose :helm_max_file_size - expose :maven_max_file_size - expose :npm_max_file_size - expose :nuget_max_file_size - expose :pypi_max_file_size - expose :terraform_module_max_file_size - expose :storage_size_limit + expose :ci_pipeline_size, documentation: { type: 'integer', example: 0 } + expose :ci_active_jobs, documentation: { type: 'integer', example: 0 } + expose :ci_active_pipelines, documentation: { type: 'integer', example: 0 } + expose :ci_project_subscriptions, documentation: { type: 'integer', example: 2 } + expose :ci_pipeline_schedules, documentation: { type: 'integer', example: 10 } + expose :ci_needs_size_limit, documentation: { type: 'integer', example: 50 } + expose :ci_registered_group_runners, documentation: { type: 'integer', example: 1000 } + expose :ci_registered_project_runners, documentation: { type: 'integer', example: 1000 } + expose :conan_max_file_size, documentation: { type: 'integer', example: 3221225472 } + expose :generic_packages_max_file_size, documentation: { type: 'integer', example: 5368709120 } + expose :helm_max_file_size, documentation: { type: 'integer', example: 5242880 } + expose :maven_max_file_size, documentation: { type: 'integer', example: 3221225472 } + expose :npm_max_file_size, documentation: { type: 'integer', example: 524288000 } + expose :nuget_max_file_size, documentation: { type: 'integer', example: 524288000 } + expose :pypi_max_file_size, documentation: { type: 'integer', example: 3221225472 } + expose :terraform_module_max_file_size, documentation: { type: 'integer', example: 1073741824 } + expose :storage_size_limit, documentation: { type: 'integer', example: 15000 } end end end diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index f158695f605..1c1bafbf161 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -5,147 +5,148 @@ module API class Project < BasicProjectDetails include ::API::Helpers::RelatedResourcesHelpers - expose :container_registry_url, as: :container_registry_image_prefix, if: -> (_, _) { Gitlab.config.registry.enabled } + expose :container_registry_url, as: :container_registry_image_prefix, documentation: { type: 'string', example: 'registry.gitlab.example.com/gitlab/gitlab-client' }, if: -> (_, _) { Gitlab.config.registry.enabled } expose :_links do - expose :self do |project| + expose :self, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4' } do |project| expose_url(api_v4_projects_path(id: project.id)) end - expose :issues, if: -> (project, options) { issues_available?(project, options) } do |project| + expose :issues, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/issues' }, if: -> (project, options) { issues_available?(project, options) } do |project| expose_url(api_v4_projects_issues_path(id: project.id)) end - expose :merge_requests, if: -> (project, options) { mrs_available?(project, options) } do |project| + expose :merge_requests, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/merge_requests' }, if: -> (project, options) { mrs_available?(project, options) } do |project| expose_url(api_v4_projects_merge_requests_path(id: project.id)) end - expose :repo_branches do |project| + expose :repo_branches, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/repository/branches' } do |project| expose_url(api_v4_projects_repository_branches_path(id: project.id)) end - expose :labels do |project| + expose :labels, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/labels' } do |project| expose_url(api_v4_projects_labels_path(id: project.id)) end - expose :events do |project| + expose :events, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/events' } do |project| expose_url(api_v4_projects_events_path(id: project.id)) end - expose :members do |project| + expose :members, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/members' } do |project| expose_url(api_v4_projects_members_path(id: project.id)) end - expose :cluster_agents do |project| + expose :cluster_agents, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/cluster_agents' } do |project| expose_url(api_v4_projects_cluster_agents_path(id: project.id)) end end - expose :packages_enabled - expose :empty_repo?, as: :empty_repo - expose :archived?, as: :archived - expose :visibility + expose :packages_enabled, documentation: { type: 'boolean' } + expose :empty_repo?, as: :empty_repo, documentation: { type: 'boolean' } + expose :archived?, as: :archived, documentation: { type: 'boolean' } + expose :visibility, documentation: { type: 'string', example: 'public' } expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } - expose :resolve_outdated_diff_discussions + expose :resolve_outdated_diff_discussions, documentation: { type: 'boolean' } expose :container_expiration_policy, using: Entities::ContainerExpirationPolicy, if: -> (project, _) { project.container_expiration_policy } # Expose old field names with the new permissions methods to keep API compatible # TODO: remove in API v5, replaced by *_access_level - expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) } - expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) } - expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) } - expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) } - expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) } - expose(:container_registry_enabled) { |project, options| project.feature_available?(:container_registry, options[:current_user]) } - expose :service_desk_enabled - expose :service_desk_address, if: -> (project, options) do + expose(:issues_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:issues, options[:current_user]) } + expose(:merge_requests_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) } + expose(:wiki_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:wiki, options[:current_user]) } + expose(:jobs_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:builds, options[:current_user]) } + expose(:snippets_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:snippets, options[:current_user]) } + expose(:container_registry_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:container_registry, options[:current_user]) } + expose :service_desk_enabled, documentation: { type: 'boolean' } + expose :service_desk_address, documentation: { type: 'string', example: 'address@example.com' }, if: -> (project, options) do Ability.allowed?(options[:current_user], :admin_issue, project) end - expose(:can_create_merge_request_in) do |project, options| + expose(:can_create_merge_request_in, documentation: { type: 'boolean' }) do |project, options| Ability.allowed?(options[:current_user], :create_merge_request_in, project) end - expose(:issues_access_level) { |project, options| project_feature_string_access_level(project, :issues) } - expose(:repository_access_level) { |project, options| project_feature_string_access_level(project, :repository) } - expose(:merge_requests_access_level) { |project, options| project_feature_string_access_level(project, :merge_requests) } - expose(:forking_access_level) { |project, options| project_feature_string_access_level(project, :forking) } - expose(:wiki_access_level) { |project, options| project_feature_string_access_level(project, :wiki) } - expose(:builds_access_level) { |project, options| project_feature_string_access_level(project, :builds) } - expose(:snippets_access_level) { |project, options| project_feature_string_access_level(project, :snippets) } - expose(:pages_access_level) { |project, options| project_feature_string_access_level(project, :pages) } - expose(:operations_access_level) { |project, options| project_feature_string_access_level(project, :operations) } - expose(:analytics_access_level) { |project, options| project_feature_string_access_level(project, :analytics) } - expose(:container_registry_access_level) { |project, options| project_feature_string_access_level(project, :container_registry) } - expose(:security_and_compliance_access_level) { |project, options| project_feature_string_access_level(project, :security_and_compliance) } - expose(:releases_access_level) { |project, options| project_feature_string_access_level(project, :releases) } - - expose :emails_disabled - expose :shared_runners_enabled - expose :lfs_enabled?, as: :lfs_enabled - expose :creator_id + expose(:issues_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :issues) } + expose(:repository_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :repository) } + expose(:merge_requests_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :merge_requests) } + expose(:forking_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :forking) } + expose(:wiki_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :wiki) } + expose(:builds_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :builds) } + expose(:snippets_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :snippets) } + expose(:pages_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :pages) } + expose(:operations_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :operations) } + expose(:analytics_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :analytics) } + expose(:container_registry_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :container_registry) } + expose(:security_and_compliance_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :security_and_compliance) } + expose(:releases_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :releases) } + + expose :emails_disabled, documentation: { type: 'boolean' } + expose :shared_runners_enabled, documentation: { type: 'boolean' } + expose :lfs_enabled?, as: :lfs_enabled, documentation: { type: 'boolean' } + expose :creator_id, documentation: { type: 'integer', example: 1 } expose :forked_from_project, using: Entities::BasicProjectDetails, if: ->(project, options) do project.forked? && Ability.allowed?(options[:current_user], :read_project, project.forked_from_project) end - expose :mr_default_target_self, if: -> (project) { project.forked? } + expose :mr_default_target_self, if: -> (project) { project.forked? }, documentation: { type: 'boolean' } - expose :import_url, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) } do |project| + expose :import_url, documentation: { type: 'string', example: 'https://gitlab.com/gitlab/gitlab.git' }, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) } do |project| project[:import_url] end - expose :import_type, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) } - expose :import_status - expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project| + expose :import_type, documentation: { type: 'string', example: 'git' }, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) } + expose :import_status, documentation: { type: 'string', example: 'none' } + expose :import_error, documentation: { type: 'string', example: 'Import error' }, if: lambda { |_project, options| options[:user_can_admin_project] } do |project| project.import_state&.last_error end - expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) } - expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } - expose :ci_default_git_depth - expose :ci_forward_deployment_enabled - expose(:ci_job_token_scope_enabled) { |p, _| p.ci_outbound_job_token_scope_enabled? } - expose :ci_separated_caches - expose :ci_opt_in_jwt - expose :ci_allow_fork_pipelines_to_run_in_parent_project - expose :public_builds, as: :public_jobs - expose :build_git_strategy, if: lambda { |project, options| options[:user_can_admin_project] } do |project, options| + expose :open_issues_count, documentation: { type: 'integer', example: 1 }, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) } + expose :runners_token, documentation: { type: 'string', example: 'b8547b1dc37721d05889db52fa2f02' }, if: lambda { |_project, options| options[:user_can_admin_project] } + expose :ci_default_git_depth, documentation: { type: 'integer', example: 20 } + expose :ci_forward_deployment_enabled, documentation: { type: 'boolean' } + expose(:ci_job_token_scope_enabled, documentation: { type: 'boolean' }) { |p, _| p.ci_outbound_job_token_scope_enabled? } + expose :ci_separated_caches, documentation: { type: 'boolean' } + expose :ci_opt_in_jwt, documentation: { type: 'boolean' } + expose :ci_allow_fork_pipelines_to_run_in_parent_project, documentation: { type: 'boolean' } + expose :public_builds, as: :public_jobs, documentation: { type: 'boolean' } + expose :build_git_strategy, documentation: { type: 'string', example: 'fetch' }, if: lambda { |project, options| options[:user_can_admin_project] } do |project, options| project.build_allow_git_fetch ? 'fetch' : 'clone' end - expose :build_timeout - expose :auto_cancel_pending_pipelines - expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) } - expose :shared_with_groups do |project, options| + expose :build_timeout, documentation: { type: 'integer', example: 3600 } + expose :auto_cancel_pending_pipelines, documentation: { type: 'string', example: 'enabled' } + expose :ci_config_path, documentation: { type: 'string', example: '' }, if: -> (project, options) { Ability.allowed?(options[:current_user], :read_code, project) } + expose :shared_with_groups, documentation: { is_array: true } do |project, options| user = options[:current_user] SharedGroupWithProject.represent(project.visible_group_links(for_user: user), options) end - expose :only_allow_merge_if_pipeline_succeeds - expose :allow_merge_on_skipped_pipeline - expose :restrict_user_defined_variables - expose :request_access_enabled - expose :only_allow_merge_if_all_discussions_are_resolved - expose :remove_source_branch_after_merge - 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 + expose :only_allow_merge_if_pipeline_succeeds, documentation: { type: 'boolean' } + expose :allow_merge_on_skipped_pipeline, documentation: { type: 'boolean' } + expose :restrict_user_defined_variables, documentation: { type: 'boolean' } + expose :request_access_enabled, documentation: { type: 'boolean' } + expose :only_allow_merge_if_all_discussions_are_resolved, documentation: { type: 'boolean' } + expose :remove_source_branch_after_merge, documentation: { type: 'boolean' } + expose :printing_merge_request_link_enabled, documentation: { type: 'boolean' } + expose :merge_method, documentation: { type: 'string', example: 'merge' } + expose :squash_option, documentation: { type: 'string', example: 'default_off' } + expose :enforce_auth_checks_on_uploads, documentation: { type: 'boolean' } + expose :suggestion_commit_message, documentation: { type: 'string', example: 'Suggestion message' } + expose :merge_commit_template, documentation: { type: 'string', example: '%(title)' } + expose :squash_commit_template, documentation: { type: 'string', example: '%(source_branch)' } + expose :issue_branch_template, documentation: { type: 'string', example: '%(title)' } expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) { options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project) } - expose :auto_devops_enabled?, as: :auto_devops_enabled - expose :auto_devops_deploy_strategy do |project, options| + expose :auto_devops_enabled?, as: :auto_devops_enabled, documentation: { type: 'boolean' } + expose :auto_devops_deploy_strategy, documentation: { type: 'string', example: 'continuous' } do |project, options| project.auto_devops.nil? ? 'continuous' : project.auto_devops.deploy_strategy end - expose :autoclose_referenced_issues - expose :repository_storage, if: ->(project, options) { + expose :autoclose_referenced_issues, documentation: { type: 'boolean' } + expose :repository_storage, documentation: { type: 'string', example: 'default' }, if: ->(project, options) { Ability.allowed?(options[:current_user], :change_repository_storage, project) } - expose :keep_latest_artifacts_available?, as: :keep_latest_artifact - expose :runner_token_expiration_interval + expose :keep_latest_artifacts_available?, as: :keep_latest_artifact, documentation: { type: 'boolean' } + expose :runner_token_expiration_interval, documentation: { type: 'integer', example: 3600 } # rubocop: disable CodeReuse/ActiveRecord def self.preload_resource(project) diff --git a/lib/api/entities/project_daily_fetches.rb b/lib/api/entities/project_daily_fetches.rb index 036b5dc99b8..8797aeb9521 100644 --- a/lib/api/entities/project_daily_fetches.rb +++ b/lib/api/entities/project_daily_fetches.rb @@ -3,8 +3,8 @@ module API module Entities class ProjectDailyFetches < Grape::Entity - expose :fetch_count, as: :count - expose :date + expose :fetch_count, as: :count, documentation: { type: 'integer', example: 3 } + expose :date, documentation: { type: 'date', example: '2022-01-01' } end end end diff --git a/lib/api/entities/project_daily_statistics.rb b/lib/api/entities/project_daily_statistics.rb index 803ee445851..555ecc6be39 100644 --- a/lib/api/entities/project_daily_statistics.rb +++ b/lib/api/entities/project_daily_statistics.rb @@ -4,8 +4,8 @@ module API module Entities class ProjectDailyStatistics < Grape::Entity expose :fetches do - expose :total_fetch_count, as: :total - expose :fetches, as: :days, using: ProjectDailyFetches + expose :total_fetch_count, as: :total, documentation: { type: 'integer', example: 3 } + expose :fetches, as: :days, using: ProjectDailyFetches, documentation: { is_array: true } end end end diff --git a/lib/api/entities/project_export_status.rb b/lib/api/entities/project_export_status.rb index ad84a45996a..9a2aeb7a6bb 100644 --- a/lib/api/entities/project_export_status.rb +++ b/lib/api/entities/project_export_status.rb @@ -5,13 +5,21 @@ module API class ProjectExportStatus < ProjectIdentity include ::API::Helpers::RelatedResourcesHelpers - expose :export_status + expose :export_status, documentation: { + type: 'string', example: 'finished', values: %w[queued started finished failed] + } expose :_links, if: lambda { |project, _options| project.export_status == :finished } do - expose :api_url do |project| + expose :api_url, documentation: { + type: 'string', + example: 'https://gitlab.example.com/api/v4/projects/1/export/download' + } do |project| expose_url(api_v4_projects_export_download_path(id: project.id)) end - expose :web_url do |project| + expose :web_url, documentation: { + type: 'string', + example: 'https://gitlab.example.com/gitlab-org/gitlab-test/download_export' + } do |project| Gitlab::Routing.url_helpers.download_export_project_url(project) end end diff --git a/lib/api/entities/project_group_link.rb b/lib/api/entities/project_group_link.rb index 89138854e67..b5d5991ec7f 100644 --- a/lib/api/entities/project_group_link.rb +++ b/lib/api/entities/project_group_link.rb @@ -3,7 +3,11 @@ module API module Entities class ProjectGroupLink < Grape::Entity - expose :id, :project_id, :group_id, :group_access, :expires_at + expose :id, documentation: { type: 'integer', example: 1 } + expose :project_id, documentation: { type: 'integer', example: 1 } + expose :group_id, documentation: { type: 'integer', example: 1 } + expose :group_access, documentation: { type: 'integer', example: 10 } + expose :expires_at, documentation: { type: 'date', example: '2016-09-26' } end end end diff --git a/lib/api/entities/project_hook.rb b/lib/api/entities/project_hook.rb index 6c71e5d317c..bffb057abed 100644 --- a/lib/api/entities/project_hook.rb +++ b/lib/api/entities/project_hook.rb @@ -3,10 +3,17 @@ module API module Entities class ProjectHook < Hook - expose :project_id, :issues_events, :confidential_issues_events - expose :note_events, :confidential_note_events, :pipeline_events, :wiki_page_events, :deployment_events - expose :job_events, :releases_events - expose :push_events_branch_filter + expose :project_id, documentation: { type: 'string', example: 1 } + expose :issues_events, documentation: { type: 'boolean' } + expose :confidential_issues_events, documentation: { type: 'boolean' } + expose :note_events, documentation: { type: 'boolean' } + expose :confidential_note_events, documentation: { type: 'boolean' } + expose :pipeline_events, documentation: { type: 'boolean' } + expose :wiki_page_events, documentation: { type: 'boolean' } + expose :deployment_events, documentation: { type: 'boolean' } + expose :job_events, documentation: { type: 'boolean' } + expose :releases_events, documentation: { type: 'boolean' } + expose :push_events_branch_filter, documentation: { type: 'string', example: 'my-branch-*' } end end end diff --git a/lib/api/entities/project_identity.rb b/lib/api/entities/project_identity.rb index 2055195eea0..14aef05b95e 100644 --- a/lib/api/entities/project_identity.rb +++ b/lib/api/entities/project_identity.rb @@ -3,10 +3,13 @@ module API module Entities class ProjectIdentity < Grape::Entity - expose :id, :description - expose :name, :name_with_namespace - expose :path, :path_with_namespace - expose :created_at + expose :id, documentation: { type: 'integer', example: 1 } + expose :description, documentation: { type: 'string', example: 'desc' } + expose :name, documentation: { type: 'string', example: 'project1' } + expose :name_with_namespace, documentation: { type: 'string', example: 'John Doe / project1' } + expose :path, documentation: { type: 'string', example: 'project1' } + expose :path_with_namespace, documentation: { type: 'string', example: 'namespace1/project1' } + expose :created_at, documentation: { type: 'dateTime', example: '2020-05-07T04:27:17.016Z' } end end end diff --git a/lib/api/entities/project_import_failed_relation.rb b/lib/api/entities/project_import_failed_relation.rb index 26cfae7260c..543cc62f364 100644 --- a/lib/api/entities/project_import_failed_relation.rb +++ b/lib/api/entities/project_import_failed_relation.rb @@ -3,14 +3,17 @@ module API module Entities class ProjectImportFailedRelation < Grape::Entity - expose :id, :created_at, :exception_class, :source + expose :id, documentation: { type: 'string', example: 1 } + expose :created_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :exception_class, documentation: { type: 'string', example: 'StandardError' } + expose :source, documentation: { type: 'string', example: 'ImportRepositoryWorker' } - expose :exception_message do |_| + expose :exception_message, documentation: { type: 'string' } do |_| nil end - expose :relation_key, as: :relation_name - expose :relation_index, as: :line_number + expose :relation_key, as: :relation_name, documentation: { type: 'string', example: 'issues' } + expose :relation_index, as: :line_number, documentation: { type: 'integer', example: 1 } end end end diff --git a/lib/api/entities/project_import_status.rb b/lib/api/entities/project_import_status.rb index 5daae4a70f2..59388aacafd 100644 --- a/lib/api/entities/project_import_status.rb +++ b/lib/api/entities/project_import_status.rb @@ -3,21 +3,25 @@ module API module Entities class ProjectImportStatus < ProjectIdentity - expose :import_status - expose :import_type - expose :correlation_id do |project, _options| + expose :import_status, documentation: { type: 'string', example: 'scheduled' } + expose :import_type, documentation: { type: 'string', example: 'gitlab_project' } + expose :correlation_id, documentation: { + type: 'string', example: 'dfcf583058ed4508e4c7c617bd7f0edd' + } do |project, _options| project.import_state&.correlation_id end - expose :failed_relations, using: Entities::ProjectImportFailedRelation do |project, _options| + expose :failed_relations, using: Entities::ProjectImportFailedRelation, documentation: { + is_array: true + } do |project, _options| project.import_state&.relation_hard_failures(limit: 100) || [] end - expose :import_error do |project, _options| + expose :import_error, documentation: { type: 'string', example: 'Error message' } do |project, _options| project.import_state&.last_error end - expose :stats do |project, _options| + expose :stats, documentation: { type: 'object' } do |project, _options| if project.github_import? ::Gitlab::GithubImport::ObjectCounter.summary(project) end diff --git a/lib/api/entities/project_integration.rb b/lib/api/entities/project_integration.rb index 155136d2f80..29bb60a19e5 100644 --- a/lib/api/entities/project_integration.rb +++ b/lib/api/entities/project_integration.rb @@ -4,7 +4,7 @@ module API module Entities class ProjectIntegration < Entities::ProjectIntegrationBasic # Expose serialized properties - expose :properties do |integration, options| + expose :properties, documentation: { type: 'Hash', example: { "token" => "secr3t" } } do |integration, options| integration.api_field_names.to_h do |name| [name, integration.public_send(name)] # rubocop:disable GitlabSecurity/PublicSend end diff --git a/lib/api/entities/project_integration_basic.rb b/lib/api/entities/project_integration_basic.rb index 2870123b83d..aa0ad158b83 100644 --- a/lib/api/entities/project_integration_basic.rb +++ b/lib/api/entities/project_integration_basic.rb @@ -3,15 +3,26 @@ module API module Entities class ProjectIntegrationBasic < Grape::Entity - expose :id, :title - expose :slug do |integration| + expose :id, documentation: { type: 'integer', example: 75 } + expose :title, documentation: { type: 'string', example: 'Jenkins CI' } + expose :slug, documentation: { type: 'integer', example: 'jenkins' } do |integration| integration.to_param.dasherize end - expose :created_at, :updated_at, :active - expose :commit_events, :push_events, :issues_events, :confidential_issues_events - expose :merge_requests_events, :tag_push_events, :note_events - expose :confidential_note_events, :pipeline_events, :wiki_page_events - expose :job_events, :comment_on_event_enabled + expose :created_at, documentation: { type: 'dateTime', example: '2019-11-20T11:20:25.297Z' } + expose :updated_at, documentation: { type: 'dateTime', example: '2019-11-20T12:24:37.498Z' } + expose :active, documentation: { type: 'boolean' } + expose :commit_events, documentation: { type: 'boolean' } + expose :push_events, documentation: { type: 'boolean' } + expose :issues_events, documentation: { type: 'boolean' } + expose :confidential_issues_events, documentation: { type: 'boolean' } + expose :merge_requests_events, documentation: { type: 'boolean' } + expose :tag_push_events, documentation: { type: 'boolean' } + expose :note_events, documentation: { type: 'boolean' } + expose :confidential_note_events, documentation: { type: 'boolean' } + expose :pipeline_events, documentation: { type: 'boolean' } + expose :wiki_page_events, documentation: { type: 'boolean' } + expose :job_events, documentation: { type: 'boolean' } + expose :comment_on_event_enabled, documentation: { type: 'boolean' } end end end diff --git a/lib/api/entities/project_repository_storage.rb b/lib/api/entities/project_repository_storage.rb index 0816bebde2c..ae5039601d7 100644 --- a/lib/api/entities/project_repository_storage.rb +++ b/lib/api/entities/project_repository_storage.rb @@ -5,12 +5,16 @@ module API class ProjectRepositoryStorage < Grape::Entity include Gitlab::Routing - expose :disk_path do |project| + expose :disk_path, documentation: { + type: 'string', + example: '@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' + } do |project| project.repository.disk_path end - expose :id, as: :project_id - expose :repository_storage, :created_at + expose :id, as: :project_id, documentation: { type: 'integer', example: 1 } + expose :repository_storage, documentation: { type: 'string', example: 'default' } + expose :created_at, documentation: { type: 'dateTime', example: '2012-10-12T17:04:47Z' } end end end diff --git a/lib/api/entities/project_with_access.rb b/lib/api/entities/project_with_access.rb index b541ccbadcf..9722b8806d4 100644 --- a/lib/api/entities/project_with_access.rb +++ b/lib/api/entities/project_with_access.rb @@ -25,23 +25,41 @@ module API # rubocop: disable CodeReuse/ActiveRecord def self.preload_relation(projects_relation, options = {}) - relation = super(projects_relation, options) - # use reselect to override the existing select and - # prevent an error `subquery has too many columns` - project_ids = relation.reselect('projects.id') - namespace_ids = relation.reselect(:namespace_id) + if ::Feature.enabled?(:projects_preloader_fix) + super(projects_relation, options) + else + relation = super(projects_relation, options) + # use reselect to override the existing select and + # prevent an error `subquery has too many columns` + project_ids = relation.reselect('projects.id') + namespace_ids = relation.reselect(:namespace_id) + + options[:project_members] = options[:current_user] + .project_members + .where(source_id: project_ids) + .preload(:source, user: [notification_settings: :source]) + + options[:group_members] = options[:current_user] + .group_members + .where(source_id: namespace_ids) + .preload(:source, user: [notification_settings: :source]) + + relation + end + end + + def self.postload_relation(projects_relation, options = {}) + return unless ::Feature.enabled?(:projects_preloader_fix) options[:project_members] = options[:current_user] .project_members - .where(source_id: project_ids) + .where(source_id: projects_relation.subquery(:id)) .preload(:source, user: [notification_settings: :source]) options[:group_members] = options[:current_user] .group_members - .where(source_id: namespace_ids) + .where(source_id: projects_relation.subquery(:namespace_id)) .preload(:source, user: [notification_settings: :source]) - - relation end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/lib/api/entities/protected_branch.rb b/lib/api/entities/protected_branch.rb index ac44d06e69c..42f721b40a6 100644 --- a/lib/api/entities/protected_branch.rb +++ b/lib/api/entities/protected_branch.rb @@ -3,11 +3,11 @@ module API module Entities class ProtectedBranch < Grape::Entity - expose :id - expose :name - expose :push_access_levels, using: Entities::ProtectedRefAccess - expose :merge_access_levels, using: Entities::ProtectedRefAccess - expose :allow_force_push + expose :id, documentation: { type: 'integer', example: 1 } + expose :name, documentation: { type: 'string', example: 'main' } + expose :push_access_levels, using: Entities::ProtectedRefAccess, documentation: { is_array: true } + expose :merge_access_levels, using: Entities::ProtectedRefAccess, documentation: { is_array: true } + expose :allow_force_push, documentation: { type: 'boolean' } end end end diff --git a/lib/api/entities/protected_ref_access.rb b/lib/api/entities/protected_ref_access.rb index 443277e23cf..ba28c724448 100644 --- a/lib/api/entities/protected_ref_access.rb +++ b/lib/api/entities/protected_ref_access.rb @@ -3,10 +3,12 @@ module API module Entities class ProtectedRefAccess < Grape::Entity - expose :access_level - expose :access_level_description do |protected_ref_access| - protected_ref_access.humanize - end + expose :id, documentation: { type: 'integer', example: 1 } + expose :access_level, documentation: { type: 'integer', example: 40 } + expose :access_level_description, + documentation: { type: 'string', example: 'Maintainers' } do |protected_ref_access| + protected_ref_access.humanize + end end end end diff --git a/lib/api/entities/protected_tag.rb b/lib/api/entities/protected_tag.rb index dc397f01af6..ba984ae79b8 100644 --- a/lib/api/entities/protected_tag.rb +++ b/lib/api/entities/protected_tag.rb @@ -3,7 +3,7 @@ module API module Entities class ProtectedTag < Grape::Entity - expose :name + expose :name, documentation: { type: 'string', example: 'release-1-0' } expose :create_access_levels, using: Entities::ProtectedRefAccess end end diff --git a/lib/api/entities/pull_mirror.rb b/lib/api/entities/pull_mirror.rb new file mode 100644 index 00000000000..72a5220987e --- /dev/null +++ b/lib/api/entities/pull_mirror.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module API + module Entities + class PullMirror < Grape::Entity + expose :id, documentation: { type: 'integer', example: 101486 } + expose :status, as: :update_status, documentation: { type: 'string', example: 'finished' } + expose :url, +documentation: { type: 'string', + example: 'https://*****:*****@gitlab.com/gitlab-org/security/gitlab.git' } do |import_state| + import_state.project.safe_import_url + end + expose :last_error, documentation: { type: 'string', example: nil } + expose :last_update_at, documentation: { type: 'dateTime', example: '2020-01-06T17:32:02.823Z' } + expose :last_update_started_at, documentation: { type: 'dateTime', example: '2020-01-06T17:32:02.823Z' } + expose :last_successful_update_at, documentation: { type: 'dateTime', example: '2020-01-06T17:32:02.823Z' } + end + end +end diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb index 2403c907f7f..c1a48a46d64 100644 --- a/lib/api/entities/release.rb +++ b/lib/api/entities/release.rb @@ -9,22 +9,24 @@ module API MarkupHelper.markdown_field(entity, :description, current_user: options[:current_user]) end expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? } - expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? } + expose :commit, using: Entities::Commit, if: ->(_, _) { can_read_code? } expose :milestones, using: Entities::MilestoneWithStats, if: -> (release, _) { release.milestones.present? && can_read_milestone? } do |release, _| release.milestones.order_by_dates_and_title end - expose :commit_path, expose_nil: false - expose :tag_path, expose_nil: false + expose :commit_path, + documentation: { type: 'string', example: '/root/app/commit/588440f66559714280628a4f9799f0c4eb880a4a' }, + expose_nil: false + expose :tag_path, documentation: { type: 'string', example: '/root/app/-/tags/v1.0' }, expose_nil: false expose :assets do - expose :assets_count, as: :count - expose :sources, using: Entities::Releases::Source, if: ->(_, _) { can_download_code? } + expose :assets_count, documentation: { type: 'integer', example: 2 }, as: :count + expose :sources, using: Entities::Releases::Source, if: ->(_, _) { can_read_code? } expose :sorted_links, as: :links, using: Entities::Releases::Link end - expose :evidences, using: Entities::Releases::Evidence, expose_nil: false, if: ->(_, _) { can_download_code? } + expose :evidences, using: Entities::Releases::Evidence, expose_nil: false, if: ->(_, _) { can_read_code? } expose :_links do expose :self_url, as: :self, expose_nil: false expose :edit_url, expose_nil: false @@ -32,8 +34,8 @@ module API private - def can_download_code? - Ability.allowed?(options[:current_user], :download_code, object.project) + def can_read_code? + Ability.allowed?(options[:current_user], :read_code, object.project) end def can_read_milestone? diff --git a/lib/api/entities/releases/evidence.rb b/lib/api/entities/releases/evidence.rb index 01603a71dbf..9d324309213 100644 --- a/lib/api/entities/releases/evidence.rb +++ b/lib/api/entities/releases/evidence.rb @@ -6,9 +6,9 @@ module API class Evidence < Grape::Entity include ::API::Helpers::Presentable - expose :sha - expose :filepath - expose :collected_at + expose :sha, documentation: { type: 'string', example: '760d6cdfb0879c3ffedec13af470e0f71cf52c6cde4d' } + expose :filepath, documentation: { type: 'string', example: 'https://gitlab.example.com/root/app/-/releases/v1.0/evidence.json' } + expose :collected_at, documentation: { type: 'dateTime', example: '2019-01-03T01:56:19.539Z' } end end end diff --git a/lib/api/entities/releases/link.rb b/lib/api/entities/releases/link.rb index 5157645af69..abf380e11d5 100644 --- a/lib/api/entities/releases/link.rb +++ b/lib/api/entities/releases/link.rb @@ -4,14 +4,22 @@ module API module Entities module Releases class Link < Grape::Entity - expose :id - expose :name - expose :url - expose :direct_asset_url do |link| + expose :id, documentation: { type: 'integer', example: 1 } + expose :name, documentation: { type: 'string', example: 'app-v1.0.dmg' } + expose :url, documentation: + { + type: 'string', + example: 'https://gitlab.example.com/root/app/-/jobs/688/artifacts/raw/bin/app-v1.0.dmg' + } + expose :direct_asset_url, documentation: + { + type: 'string', + example: 'https://gitlab.example.com/root/app/-/releases/v1.0/downloads/app-v1.0.dmg' + } do |link| ::Releases::LinkPresenter.new(link).direct_asset_url end - expose :external?, as: :external - expose :link_type + expose :external?, documentation: { type: 'boolean' }, as: :external + expose :link_type, documentation: { type: 'string', example: 'other' } end end end diff --git a/lib/api/entities/releases/source.rb b/lib/api/entities/releases/source.rb index 2b0c8038ddf..8c6750d6142 100644 --- a/lib/api/entities/releases/source.rb +++ b/lib/api/entities/releases/source.rb @@ -4,8 +4,8 @@ module API module Entities module Releases class Source < Grape::Entity - expose :format - expose :url + expose :format, documentation: { type: 'string', example: 'zip' } + expose :url, documentation: { type: 'string', example: 'https://gitlab.example.com/root/app/-/archive/v1.0/app-v1.0.zip' } end end end diff --git a/lib/api/entities/remote_mirror.rb b/lib/api/entities/remote_mirror.rb index 87daef9a05c..9fb5b2697bc 100644 --- a/lib/api/entities/remote_mirror.rb +++ b/lib/api/entities/remote_mirror.rb @@ -3,16 +3,16 @@ module API module Entities class RemoteMirror < Grape::Entity - expose :id - expose :enabled - expose :safe_url, as: :url - expose :update_status - expose :last_update_at - expose :last_update_started_at - expose :last_successful_update_at - expose :last_error - expose :only_protected_branches - expose :keep_divergent_refs + expose :id, documentation: { type: 'integer', example: 101486 } + expose :enabled, documentation: { type: 'boolean', example: true } + expose :safe_url, as: :url, documentation: { type: 'string', example: 'https://*****:*****@example.com/gitlab/example.git' } + expose :update_status, documentation: { type: 'string', example: 'finished' } + expose :last_update_at, documentation: { type: 'dateTime', example: '2020-01-06T17:32:02.823Z' } + expose :last_update_started_at, documentation: { type: 'dateTime', example: '2020-01-06T17:32:02.823Z' } + expose :last_successful_update_at, documentation: { type: 'dateTime', example: '2020-01-06T17:31:55.864Z' } + expose :last_error, documentation: { type: 'integer', example: 'The remote mirror URL is invalid.' } + expose :only_protected_branches, documentation: { type: 'boolean' } + expose :keep_divergent_refs, documentation: { type: 'boolean' } end end end diff --git a/lib/api/entities/resource_access_token.rb b/lib/api/entities/resource_access_token.rb index 569fd16f488..e4f140d3fc0 100644 --- a/lib/api/entities/resource_access_token.rb +++ b/lib/api/entities/resource_access_token.rb @@ -3,7 +3,12 @@ module API module Entities class ResourceAccessToken < Entities::PersonalAccessToken - expose :access_level do |token, options| + expose :access_level, + documentation: { type: 'integer', + example: 40, + description: 'Access level. Valid values are 10 (Guest), 20 (Reporter), 30 (Developer) \ + , 40 (Maintainer), and 50 (Owner). Defaults to 40.', + values: [10, 20, 30, 40, 50] } do |token, options| options[:resource].member(token.user).access_level end end diff --git a/lib/api/entities/resource_milestone_event.rb b/lib/api/entities/resource_milestone_event.rb index 26dc6620cbe..b301f5b7d0a 100644 --- a/lib/api/entities/resource_milestone_event.rb +++ b/lib/api/entities/resource_milestone_event.rb @@ -3,18 +3,18 @@ module API module Entities class ResourceMilestoneEvent < Grape::Entity - expose :id + expose :id, documentation: { type: 'integer', example: 142 } expose :user, using: Entities::UserBasic - expose :created_at - expose :resource_type do |event, _options| + expose :created_at, documentation: { type: 'dateTime', example: '2018-08-20T13:38:20.077Z' } + expose :resource_type, documentation: { type: 'string', example: 'Issue' } do |event, _options| event.issuable.class.name end - expose :resource_id do |event, _options| + expose :resource_id, documentation: { type: 'integer', example: 253 } do |event, _options| event.issuable.id end expose :milestone, using: Entities::Milestone - expose :action - expose :state + expose :action, documentation: { type: 'string', example: 'add' } + expose :state, documentation: { type: 'string', example: 'active' } end end end diff --git a/lib/api/entities/snippet.rb b/lib/api/entities/snippet.rb index af885aaf0eb..709566944ed 100644 --- a/lib/api/entities/snippet.rb +++ b/lib/api/entities/snippet.rb @@ -3,11 +3,13 @@ module API module Entities class Snippet < BasicSnippet - expose :author, using: Entities::UserBasic - expose :file_name do |snippet| + expose :author, using: Entities::UserBasic, documentation: { type: 'Entities::UserBasic' } + expose :file_name, documentation: { type: 'string', example: 'add.rb' } do |snippet| snippet_files.first || snippet.file_name end - expose :files do |snippet, options| + expose :files, documentation: { + is_array: true, example: 'e0d123e5f316bef78bfdf5a008837577' + } do |snippet, options| snippet_files.map do |file| { path: file, diff --git a/lib/api/entities/snippets/repository_storage_move.rb b/lib/api/entities/snippets/repository_storage_move.rb index 4e14d1dfba2..711d07545fb 100644 --- a/lib/api/entities/snippets/repository_storage_move.rb +++ b/lib/api/entities/snippets/repository_storage_move.rb @@ -4,7 +4,7 @@ module API module Entities module Snippets class RepositoryStorageMove < BasicRepositoryStorageMove - expose :snippet, using: Entities::BasicSnippet + expose :snippet, using: Entities::BasicSnippet, documentation: { type: 'Entities::BasicSnippet' } end end end diff --git a/lib/api/entities/ssh_key.rb b/lib/api/entities/ssh_key.rb index e1554730cb6..3db10bb8ec2 100644 --- a/lib/api/entities/ssh_key.rb +++ b/lib/api/entities/ssh_key.rb @@ -3,8 +3,15 @@ module API module Entities class SSHKey < Grape::Entity - expose :id, :title, :created_at, :expires_at - expose :publishable_key, as: :key + expose :id, documentation: { type: 'integer', example: 1 } + expose :title, documentation: { type: 'string', example: 'Sample key 25' } + expose :created_at, documentation: { type: 'dateTime', example: '2015-09-03T07:24:44.627Z' } + expose :expires_at, documentation: { type: 'dateTime', example: '2020-09-03T07:24:44.627Z' } + expose :publishable_key, as: :key, documentation: + { type: 'string', + example: 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1256k6Yjz\ + GGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCdd\ + NaP0L+hM7zhFNzjFvpaMgJw0=' } end end end diff --git a/lib/api/entities/tag.rb b/lib/api/entities/tag.rb index 2d3569bb9bb..713bae64d5c 100644 --- a/lib/api/entities/tag.rb +++ b/lib/api/entities/tag.rb @@ -3,7 +3,9 @@ module API module Entities class Tag < Grape::Entity - expose :name, :message, :target + expose :name, documentation: { type: 'string', example: 'v1.0.0' } + expose :message, documentation: { type: 'string', example: 'Release v1.0.0' } + expose :target, documentation: { type: 'string', example: '2695effb5807a22ff3d138d593fd856244e155e7' } expose :commit, using: Entities::Commit do |repo_tag, options| options[:project].repository.commit(repo_tag.dereferenced_target) @@ -15,7 +17,7 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - expose :protected do |repo_tag, options| + expose :protected, documentation: { type: 'boolean', example: true } do |repo_tag, options| ::ProtectedTag.protected?(options[:project], repo_tag.name) end end diff --git a/lib/api/entities/tag_release.rb b/lib/api/entities/tag_release.rb index d5f73d60332..66d1eeeab4a 100644 --- a/lib/api/entities/tag_release.rb +++ b/lib/api/entities/tag_release.rb @@ -4,8 +4,8 @@ module API module Entities # deprecated old Release representation class TagRelease < Grape::Entity - expose :tag, as: :tag_name - expose :description + expose :tag, as: :tag_name, documentation: { type: 'string', example: '1.0.0' } + expose :description, documentation: { type: 'string', example: 'Amazing release. Wow' } end end end diff --git a/lib/api/entities/templates_list.rb b/lib/api/entities/templates_list.rb index 8e8aa1bd285..eba80bd04d3 100644 --- a/lib/api/entities/templates_list.rb +++ b/lib/api/entities/templates_list.rb @@ -3,8 +3,8 @@ module API module Entities class TemplatesList < Grape::Entity - expose :key - expose :name + expose :key, documentation: { type: 'string', example: 'mit' } + expose :name, documentation: { type: 'string', example: 'MIT License' } end end end diff --git a/lib/api/entities/tree_object.rb b/lib/api/entities/tree_object.rb index e4e840ebe43..1f542885169 100644 --- a/lib/api/entities/tree_object.rb +++ b/lib/api/entities/tree_object.rb @@ -3,9 +3,12 @@ module API module Entities class TreeObject < Grape::Entity - expose :id, :name, :type, :path + expose :id, documentation: { example: 'a1e8f8d745cc87e3a9248358d9352bb7f9a0aeba' } + expose :name, documentation: { example: 'html' } + expose :type, documentation: { example: 'tree' } + expose :path, documentation: { example: 'files/html' } - expose :mode do |obj, options| + expose :mode, documentation: { example: '040000' } do |obj, options| filemode = obj.mode filemode = "0" + filemode if filemode.length < 6 filemode diff --git a/lib/api/entities/trigger.rb b/lib/api/entities/trigger.rb index 6a9f772fc6b..ccdaeb6a07a 100644 --- a/lib/api/entities/trigger.rb +++ b/lib/api/entities/trigger.rb @@ -5,10 +5,12 @@ module API class Trigger < Grape::Entity include ::API::Helpers::Presentable - expose :id - expose :token - expose :description - expose :created_at, :updated_at, :last_used + expose :id, documentation: { type: 'integer', example: 10 } + expose :token, documentation: { type: 'string', example: '6d056f63e50fe6f8c5f8f4aa10edb7' } + expose :description, documentation: { type: 'string', example: 'test' } + expose :created_at, documentation: { type: 'dateTime', example: '2015-12-24T15:51:21.880Z' } + expose :updated_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:31.198Z' } + expose :last_used, documentation: { type: 'dateTime', example: '2015-12-24T17:54:31.198Z' } expose :owner, using: Entities::UserBasic end end diff --git a/lib/api/entities/user_agent_detail.rb b/lib/api/entities/user_agent_detail.rb index a2d02c16589..eb6d909794e 100644 --- a/lib/api/entities/user_agent_detail.rb +++ b/lib/api/entities/user_agent_detail.rb @@ -3,9 +3,9 @@ module API module Entities class UserAgentDetail < Grape::Entity - expose :user_agent - expose :ip_address - expose :submitted, as: :akismet_submitted + expose :user_agent, documentation: { type: 'string', example: 'AppleWebKit/537.36' } + expose :ip_address, documentation: { type: 'string', example: '127.0.0.1' } + expose :submitted, as: :akismet_submitted, documentation: { type: 'boolean', example: false } end end end diff --git a/lib/api/entities/user_associations_count.rb b/lib/api/entities/user_associations_count.rb new file mode 100644 index 00000000000..af744d2d49a --- /dev/null +++ b/lib/api/entities/user_associations_count.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module API + module Entities + class UserAssociationsCount < Grape::Entity + expose :groups_count do |user| + user.groups.size + end + + expose :projects_count do |user| + user.projects.size + end + + expose :issues_count do |user| + user.issues.size + end + + expose :merge_requests_count do |user| + user.merge_requests.size + end + end + end +end diff --git a/lib/api/entities/user_basic.rb b/lib/api/entities/user_basic.rb index b8ee4e5a6e0..32e066b9f7e 100644 --- a/lib/api/entities/user_basic.rb +++ b/lib/api/entities/user_basic.rb @@ -3,16 +3,25 @@ module API module Entities class UserBasic < UserSafe - expose :state + expose :state, documentation: { type: 'string', example: 'active' } - expose :avatar_url do |user, options| + expose :avatar_url, documentation: { type: 'string', example: 'https://gravatar.com/avatar/1' } do |user, options| user.avatar_url(only_path: false) end - expose :avatar_path, if: ->(user, options) { options.fetch(:only_path, false) && user.avatar_path } - expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes + expose( + :avatar_path, + documentation: { + type: 'string', + example: '/user/avatar/28/The-Big-Lebowski-400-400.png' + }, + if: ->(user, options) { options.fetch(:only_path, false) && user.avatar_path } + ) - expose :web_url do |user, options| + expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes, + documentation: { is_array: true } + + expose :web_url, documentation: { type: 'string', example: 'https://gitlab.example.com/root' } do |user, options| Gitlab::Routing.url_helpers.user_url(user) end end diff --git a/lib/api/entities/user_counts.rb b/lib/api/entities/user_counts.rb new file mode 100644 index 00000000000..e86454c249b --- /dev/null +++ b/lib/api/entities/user_counts.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module API + module Entities + class UserCounts < Grape::Entity + expose( + :assigned_open_merge_requests_count, # @deprecated + as: :merge_requests, + documentation: { type: 'integer', example: 10 } + ) + expose :assigned_open_issues_count, as: :assigned_issues, documentation: { type: 'integer', example: 10 } + expose( + :assigned_open_merge_requests_count, + as: :assigned_merge_requests, + documentation: { type: 'integer', example: 10 } + ) + expose( + :review_requested_open_merge_requests_count, + as: :review_requested_merge_requests, + documentation: { type: 'integer', example: 10 } + ) + expose :todos_pending_count, as: :todos, documentation: { type: 'integer', example: 10 } + end + end +end diff --git a/lib/api/entities/user_public.rb b/lib/api/entities/user_public.rb index 5d0e464abe1..eda72d2cfc6 100644 --- a/lib/api/entities/user_public.rb +++ b/lib/api/entities/user_public.rb @@ -3,17 +3,23 @@ module API module Entities class UserPublic < Entities::User - expose :last_sign_in_at - expose :confirmed_at - expose :last_activity_on - expose :email - expose :theme_id, :color_scheme_id, :projects_limit, :current_sign_in_at + expose :last_sign_in_at, documentation: { type: 'dateTime', example: '2015-09-03T07:24:01.670Z' } + expose :confirmed_at, documentation: { type: 'dateTime', example: '2015-09-03T07:24:01.670Z' } + expose :last_activity_on, documentation: { type: 'dateTime', example: '2015-09-03T07:24:01.670Z' } + expose :email, documentation: { type: 'string', example: 'john@example.com' } + expose :theme_id, documentation: { type: 'integer', example: 2 } + expose :color_scheme_id, documentation: { type: 'integer', example: 1 } + expose :projects_limit, documentation: { type: 'integer', example: 10 } + expose :current_sign_in_at, documentation: { type: 'dateTime', example: '2015-09-03T07:24:01.670Z' } expose :identities, using: Entities::Identity - expose :can_create_group?, as: :can_create_group - expose :can_create_project?, as: :can_create_project - expose :two_factor_enabled?, as: :two_factor_enabled + expose :can_create_group?, as: :can_create_group, documentation: { type: 'boolean', example: true } + expose :can_create_project?, as: :can_create_project, documentation: { type: 'boolean', example: true } + + expose :two_factor_enabled?, as: :two_factor_enabled, documentation: { type: 'boolean', example: true } + expose :external - expose :private_profile + + expose :private_profile, documentation: { type: 'boolean', example: :null } expose :commit_email_or_default, as: :commit_email end end diff --git a/lib/api/entities/user_safe.rb b/lib/api/entities/user_safe.rb index 127a8ef2160..0fbb10307cf 100644 --- a/lib/api/entities/user_safe.rb +++ b/lib/api/entities/user_safe.rb @@ -5,8 +5,9 @@ module API class UserSafe < Grape::Entity include RequestAwareEntity - expose :id, :username - expose :name do |user| + expose :id, documentation: { type: 'integer', example: 1 } + expose :username, documentation: { type: 'string', example: 'admin' } + expose :name, documentation: { type: 'string', example: 'Administrator' } do |user| current_user = request.respond_to?(:current_user) ? request.current_user : options.fetch(:current_user, nil) user.redacted_name(current_user) diff --git a/lib/api/entities/wiki_attachment.rb b/lib/api/entities/wiki_attachment.rb index 03a6cc8d644..8629261cfc6 100644 --- a/lib/api/entities/wiki_attachment.rb +++ b/lib/api/entities/wiki_attachment.rb @@ -5,12 +5,16 @@ module API class WikiAttachment < Grape::Entity include Gitlab::FileMarkdownLinkBuilder - expose :file_name - expose :file_path - expose :branch + expose :file_name, documentation: { type: 'string', example: 'dk.png' } + expose :file_path, documentation: { type: 'string', example: 'uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png' } + expose :branch, documentation: { type: 'string', example: 'main' } expose :link do - expose :file_path, as: :url - expose :markdown do |_entity| + expose :file_path, as: :url, documentation: { + type: 'string', example: 'uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png' + } + expose :markdown, documentation: { + type: 'string', example: '![dk](uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png)' + } do |_entity| self.markdown_link end end diff --git a/lib/api/entities/wiki_page.rb b/lib/api/entities/wiki_page.rb index 5bba4271396..07ef4a4a156 100644 --- a/lib/api/entities/wiki_page.rb +++ b/lib/api/entities/wiki_page.rb @@ -5,7 +5,9 @@ module API class WikiPage < WikiPageBasic include ::MarkupHelper - expose :content do |wiki_page, options| + expose :content, documentation: { + type: 'string', example: 'Here is an instruction how to deploy this project.' + } do |wiki_page, options| if options[:render_html] render_wiki_content( wiki_page, @@ -17,7 +19,7 @@ module API end end - expose :encoding do |wiki_page| + expose :encoding, documentation: { type: 'string', example: 'UTF-8' } do |wiki_page| wiki_page.content.encoding.name end end diff --git a/lib/api/entities/wiki_page_basic.rb b/lib/api/entities/wiki_page_basic.rb index e10c0e6d553..088a0d1bf55 100644 --- a/lib/api/entities/wiki_page_basic.rb +++ b/lib/api/entities/wiki_page_basic.rb @@ -3,9 +3,9 @@ module API module Entities class WikiPageBasic < Grape::Entity - expose :format - expose :slug - expose :title + expose :format, documentation: { type: 'string', example: 'markdown' } + expose :slug, documentation: { type: 'string', example: 'deploy' } + expose :title, documentation: { type: 'string', example: 'deploy' } end end end diff --git a/lib/api/entities/x509_certificate.rb b/lib/api/entities/x509_certificate.rb index aad11339148..95d4948906e 100644 --- a/lib/api/entities/x509_certificate.rb +++ b/lib/api/entities/x509_certificate.rb @@ -3,13 +3,16 @@ module API module Entities class X509Certificate < Grape::Entity - expose :id - expose :subject - expose :subject_key_identifier - expose :email - expose :serial_number - expose :certificate_status - expose :x509_issuer, using: 'API::Entities::X509Issuer' + expose :id, documentation: { type: 'integer', example: 1 } + expose :subject, documentation: { type: 'string', example: 'CN=gitlab@example.org,OU=Example,O=World' } + expose :subject_key_identifier, documentation: { + type: 'string', + example: 'BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC' + } + expose :email, documentation: { type: 'string', example: 'gitlab@example.org' } + expose :serial_number, documentation: { type: 'integer', example: 278969561018901340486471282831158785578 } + expose :certificate_status, documentation: { type: 'string', example: 'good' } + expose :x509_issuer, using: 'API::Entities::X509Issuer', documentation: { type: 'string', example: '100755' } end end end diff --git a/lib/api/entities/x509_issuer.rb b/lib/api/entities/x509_issuer.rb index b480bc107bc..22429560c72 100644 --- a/lib/api/entities/x509_issuer.rb +++ b/lib/api/entities/x509_issuer.rb @@ -3,10 +3,13 @@ module API module Entities class X509Issuer < Grape::Entity - expose :id - expose :subject - expose :subject_key_identifier - expose :crl_url + expose :id, documentation: { type: 'integer', example: 1 } + expose :subject, documentation: { type: 'string', example: 'CN=PKI,OU=Example,O=World' } + expose :subject_key_identifier, documentation: { + type: 'string', + example: 'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB' + } + expose :crl_url, documentation: { type: 'string', example: 'http://example.com/pki.crl' } end end end diff --git a/lib/api/entities/x509_signature.rb b/lib/api/entities/x509_signature.rb index 909b630288c..c3f0cb3659d 100644 --- a/lib/api/entities/x509_signature.rb +++ b/lib/api/entities/x509_signature.rb @@ -3,7 +3,7 @@ module API module Entities class X509Signature < Grape::Entity - expose :verification_status + expose :verification_status, documentation: { type: 'string', example: 'unverified' } expose :x509_certificate, using: 'API::Entities::X509Certificate' end end diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 42d5e6a73b3..01d46ee7bfb 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -5,24 +5,35 @@ module API class Environments < ::API::Base include PaginationParams + environments_tags = %w[environments] + before { authenticate! } feature_category :continuous_delivery urgency :low params do - requires :id, type: String, desc: 'The project ID' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project owned by the authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get all environments of the project' do - detail 'This feature was introduced in GitLab 8.11.' + desc 'List environments' do + detail 'Get all environments for a given project. This feature was introduced in GitLab 8.11.' success Entities::Environment + is_array true + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags environments_tags end params do use :pagination - optional :name, type: String, desc: 'Returns the environment with this name' - optional :search, type: String, desc: 'Returns list of environments matching the search criteria' - optional :states, type: String, values: Environment.valid_states.map(&:to_s), desc: 'List all environments that match a specific state' + optional :name, type: String, desc: 'Return the environment with this name. Mutually exclusive with search' + optional :search, type: String, desc: 'Return list of environments matching the search criteria. Mutually exclusive with name' + optional :states, + type: String, + values: Environment.valid_states.map(&:to_s), + desc: 'List all environments that match a specific state. Accepted values: `available`, `stopping`, or `stopped`. If no state value given, returns all environments' mutually_exclusive :name, :search, message: 'cannot be used together' end get ':id/environments' do @@ -33,15 +44,21 @@ module API present paginate(environments), with: Entities::Environment, current_user: current_user end - desc 'Creates a new environment' do - detail 'This feature was introduced in GitLab 8.11.' + desc 'Create a new environment' do + detail 'Creates a new environment with the given name and `external_url`. It returns `201` if the environment was successfully created, `400` for wrong parameters. This feature was introduced in GitLab 8.11.' success Entities::Environment + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags environments_tags end params do - requires :name, type: String, desc: 'The name of the environment to be created' - optional :external_url, type: String, desc: 'URL on which this deployment is viewable' + requires :name, type: String, desc: 'The name of the environment' + optional :external_url, type: String, desc: 'Place to link to for this environment' optional :slug, absence: { message: "is automatically generated and cannot be changed" }, documentation: { hidden: true } - optional :tier, type: String, values: Environment.tiers.keys, desc: 'The tier of the environment to be created' + optional :tier, type: String, values: Environment.tiers.keys, desc: 'The tier of the new environment. Allowed values are `production`, `staging`, `testing`, `development`, and `other`' end post ':id/environments' do authorize! :create_environment, user_project @@ -55,17 +72,23 @@ module API end end - desc 'Updates an existing environment' do - detail 'This feature was introduced in GitLab 8.11.' + desc 'Update an existing environment' do + detail 'Updates an existing environment name and/or `external_url`. It returns `200` if the environment was successfully updated. In case of an error, a status code `400` is returned. This feature was introduced in GitLab 8.11.' success Entities::Environment + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags environments_tags end params do - requires :environment_id, type: Integer, desc: 'The environment ID' + requires :environment_id, type: Integer, desc: 'The ID of the environment' # TODO: disallow renaming via the API https://gitlab.com/gitlab-org/gitlab/-/issues/338897 optional :name, type: String, desc: 'DEPRECATED: Renaming environment can lead to errors, this will be removed in 15.0' optional :external_url, type: String, desc: 'The new URL on which this deployment is viewable' optional :slug, absence: { message: "is automatically generated and cannot be changed" }, documentation: { hidden: true } - optional :tier, type: String, values: Environment.tiers.keys, desc: 'The tier of the environment to be created' + optional :tier, type: String, values: Environment.tiers.keys, desc: 'The tier of the new environment. Allowed values are `production`, `staging`, `testing`, `development`, and `other`' end put ':id/environments/:environment_id' do authorize! :update_environment, user_project @@ -80,14 +103,21 @@ module API end end - desc "Delete multiple stopped review apps" do - detail "Remove multiple stopped review environments older than a specific age" + desc 'Delete multiple stopped review apps' do + detail 'It schedules for deletion multiple environments that have already been stopped and are in the review app folder. The actual deletion is performed after 1 week from the time of execution. By default, it only deletes environments 30 days or older. You can change this default using the `before` parameter.' success Entities::EnvironmentBasic + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Conflict' } + ] + tags environments_tags end params do - optional :before, type: Time, desc: "The timestamp before which environments can be deleted. Defaults to 30 days ago.", default: -> { 30.days.ago } - optional :limit, type: Integer, desc: "Maximum number of environments to delete. Defaults to 100.", default: 100, values: 1..1000 - optional :dry_run, type: Boolean, desc: "If set, perform a dry run where no actual deletions will be performed. Defaults to true.", default: true + optional :before, type: Time, desc: "The date before which environments can be deleted. Defaults to 30 days ago. Expected in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`)", default: -> { 30.days.ago } + optional :limit, type: Integer, desc: "Maximum number of environments to delete. Defaults to 100", default: 100, values: 1..1000 + optional :dry_run, type: Boolean, desc: "Defaults to true for safety reasons. It performs a dry run where no actual deletion will be performed. Set to false to actually delete the environment", default: true end delete ":id/environments/review_apps" do authorize! :read_environment, user_project @@ -107,12 +137,17 @@ module API end end - desc 'Deletes an existing environment' do - detail 'This feature was introduced in GitLab 8.11.' + desc 'Delete an environment' do + detail 'It returns 204 if the environment was successfully deleted, and 404 if the environment does not exist. This feature was introduced in GitLab 8.11.' success Entities::Environment + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[environments] end params do - requires :environment_id, type: Integer, desc: 'The environment ID' + requires :environment_id, type: Integer, desc: 'The ID of the environment' end delete ':id/environments/:environment_id' do authorize! :read_environment, user_project @@ -123,12 +158,18 @@ module API destroy_conditionally!(environment) end - desc 'Stops an existing environment' do + desc 'Stop an environment' do + detail 'It returns 200 if the environment was successfully stopped, and 404 if the environment does not exist.' success Entities::Environment + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[environments] end params do - requires :environment_id, type: Integer, desc: 'The environment ID' - optional :force, type: Boolean, default: false + requires :environment_id, type: Integer, desc: 'The ID of the environment' + optional :force, type: Boolean, default: false, desc: 'Force environment to stop without executing `on_stop` actions' end post ':id/environments/:environment_id/stop' do authorize! :read_environment, user_project @@ -141,11 +182,16 @@ module API present environment, with: Entities::Environment, current_user: current_user end - desc 'Get a single environment' do + desc 'Get a specific environment' do success Entities::Environment + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[environments] end params do - requires :environment_id, type: Integer, desc: 'The environment ID' + requires :environment_id, type: Integer, desc: 'The ID of the environment' end get ':id/environments/:environment_id' do authorize! :read_environment, user_project diff --git a/lib/api/error_tracking/client_keys.rb b/lib/api/error_tracking/client_keys.rb index c1c378111a7..8a0a5e2a9b7 100644 --- a/lib/api/error_tracking/client_keys.rb +++ b/lib/api/error_tracking/client_keys.rb @@ -4,11 +4,14 @@ module API class ErrorTracking::ClientKeys < ::API::Base before { authenticate! } + ERROR_TRACKING_CLIENT_KEYS_TAGS = %w[error_tracking_client_keys].freeze + feature_category :error_tracking urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], + desc: 'The ID or URL-encoded path of the project owned by the authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do @@ -17,9 +20,11 @@ module API authorize! :admin_operations, user_project end - desc 'List all client keys' do - detail 'This feature was introduced in GitLab 14.3.' + desc 'List project client keys' do + detail 'List all client keys. This feature was introduced in GitLab 14.3.' success Entities::ErrorTracking::ClientKey + is_array true + tags ERROR_TRACKING_CLIENT_KEYS_TAGS end get '/client_keys' do collection = user_project.error_tracking_client_keys @@ -28,8 +33,10 @@ module API end desc 'Create a client key' do - detail 'This feature was introduced in GitLab 14.3.' + detail 'Creates a new client key for a project. The public key attribute is generated automatically.'\ + 'This feature was introduced in GitLab 14.3.' success Entities::ErrorTracking::ClientKey + tags ERROR_TRACKING_CLIENT_KEYS_TAGS end post '/client_keys' do key = user_project.error_tracking_client_keys.create! @@ -38,8 +45,14 @@ module API end desc 'Delete a client key' do - detail 'This feature was introduced in GitLab 14.3.' + detail 'Removes a client key from the project. This feature was introduced in GitLab 14.3.' success Entities::ErrorTracking::ClientKey + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags ERROR_TRACKING_CLIENT_KEYS_TAGS end delete '/client_keys/:key_id' do key = user_project.error_tracking_client_keys.find(params[:key_id]) diff --git a/lib/api/error_tracking/collector.rb b/lib/api/error_tracking/collector.rb index eea0fd2bce9..e10125e02c6 100644 --- a/lib/api/error_tracking/collector.rb +++ b/lib/api/error_tracking/collector.rb @@ -67,7 +67,7 @@ module API detail 'This feature was introduced in GitLab 14.1.' end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end post 'error_tracking/collector/api/:id/envelope' do # There is a reason why we have such uncommon path. @@ -119,7 +119,7 @@ module API detail 'This feature was introduced in GitLab 14.1.' end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end post 'error_tracking/collector/api/:id/store' do # There is a reason why we have such uncommon path. diff --git a/lib/api/error_tracking/project_settings.rb b/lib/api/error_tracking/project_settings.rb index fefc2098137..ec1d6a8b87f 100644 --- a/lib/api/error_tracking/project_settings.rb +++ b/lib/api/error_tracking/project_settings.rb @@ -4,6 +4,8 @@ module API class ErrorTracking::ProjectSettings < ::API::Base before { authenticate! } + ERROR_TRACKING_PROJECT_SETTINGS_TAGS = %w[error_tracking_project_settings].freeze + feature_category :error_tracking urgency :low @@ -14,7 +16,8 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], + desc: 'The ID or URL-encoded path of the project owned by the authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do @@ -24,22 +27,35 @@ module API not_found!('Error Tracking Setting') unless project_setting end - desc 'Get error tracking settings for the project' do - detail 'This feature was introduced in GitLab 12.7.' + desc 'Get Error Tracking settings' do + detail 'Get error tracking settings for the project. This feature was introduced in GitLab 12.7.' success Entities::ErrorTracking::ProjectSetting + tags ERROR_TRACKING_PROJECT_SETTINGS_TAGS end get ':id/error_tracking/settings' do present project_setting, with: Entities::ErrorTracking::ProjectSetting end - desc 'Enable or disable error tracking settings for the project' do - detail 'This feature was introduced in GitLab 12.8.' + desc 'Enable or disable the Error Tracking project settings' do + detail 'The API allows you to enable or disable the Error Tracking settings for a project.'\ + 'Only for users with the Maintainer role for the project.' success Entities::ErrorTracking::ProjectSetting + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags ERROR_TRACKING_PROJECT_SETTINGS_TAGS end params do - requires :active, type: Boolean, desc: 'Specifying whether to enable or disable error tracking settings', allow_blank: false - optional :integrated, type: Boolean, desc: 'Specifying whether to enable or disable integrated error tracking' + requires :active, + type: Boolean, + desc: 'Pass true to enable the already configured Error Tracking settings or false to disable it.', + allow_blank: false + optional :integrated, + type: Boolean, + desc: 'Pass true to enable the integrated Error Tracking backend. Available in GitLab 14.2 and later.' end patch ':id/error_tracking/settings/' do diff --git a/lib/api/feature_flags.rb b/lib/api/feature_flags.rb index 67e96284449..1846ddf6833 100644 --- a/lib/api/feature_flags.rb +++ b/lib/api/feature_flags.rb @@ -4,6 +4,8 @@ module API class FeatureFlags < ::API::Base include PaginationParams + feature_flags_tags = %w[feature_flags] + FEATURE_FLAG_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS .merge(name: API::NO_SLASH_URL_PART_REGEX) @@ -15,18 +17,24 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :feature_flags do - desc 'Get all feature flags of a project' do - detail 'This feature was introduced in GitLab 12.5' + desc 'List feature flags for a project' do + detail 'Gets all feature flags of the requested project. This feature was introduced in GitLab 12.5.' success ::API::Entities::FeatureFlag + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags feature_flags_tags end params do optional :scope, type: String, - desc: 'The scope of feature flags', + desc: 'The scope of feature flags, one of: `enabled`, `disabled`', values: %w[enabled disabled] use :pagination end @@ -39,22 +47,23 @@ module API end desc 'Create a new feature flag' do - detail 'This feature was introduced in GitLab 12.5' + detail 'Creates a new feature flag. This feature was introduced in GitLab 12.5.' success ::API::Entities::FeatureFlag + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' } + ] + tags feature_flags_tags end params do - requires :name, type: String, desc: 'The name of feature flag' + requires :name, type: String, desc: 'The name of the feature flag' optional :description, type: String, desc: 'The description of the feature flag' - optional :active, type: Boolean, desc: 'Active/inactive value of the flag' - optional :version, type: String, desc: 'The version of the feature flag' - optional :scopes, type: Array do - requires :environment_scope, type: String, desc: 'The environment scope of the scope' - requires :active, type: Boolean, desc: 'Active/inactive of the scope' - requires :strategies, type: JSON, desc: 'The strategies of the scope' - end + optional :active, type: Boolean, desc: 'The active state of the flag. Defaults to `true`. Supported in GitLab 13.3 and later' + optional :version, type: String, desc: 'The version of the feature flag. Must be `new_version_flag`. Omit to create a Legacy feature flag.' optional :strategies, type: Array do - requires :name, type: String, desc: 'The strategy name' - requires :parameters, type: JSON, desc: 'The strategy parameters' + requires :name, type: String, desc: 'The strategy name. Can be `default`, `gradualRolloutUserId`, `userWithId`, or `gitlabUserList`. In GitLab 13.5 and later, can be `flexibleRollout`' + requires :parameters, type: JSON, desc: 'The strategy parameters as a JSON-formatted string e.g. `{"userIds":"user1"}`', documentation: { type: 'String' } optional :scopes, type: Array do requires :environment_scope, type: String, desc: 'The environment scope of the scope' end @@ -87,9 +96,14 @@ module API requires :feature_flag_name, type: String, desc: 'The name of the feature flag' end resource 'feature_flags/:feature_flag_name', requirements: FEATURE_FLAG_ENDPOINT_REQUIREMENTS do - desc 'Get a feature flag of a project' do - detail 'This feature was introduced in GitLab 12.5' + desc 'Get a single feature flag' do + detail 'Gets a single feature flag. This feature was introduced in GitLab 12.5.' success ::API::Entities::FeatureFlag + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags feature_flags_tags end get do authorize_read_feature_flag! @@ -99,20 +113,27 @@ module API end desc 'Update a feature flag' do - detail 'This feature was introduced in GitLab 13.2' + detail 'Updates a feature flag. This feature was introduced in GitLab 13.2.' success ::API::Entities::FeatureFlag + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags feature_flags_tags end params do - optional :name, type: String, desc: 'The name of the feature flag' + optional :name, type: String, desc: 'The new name of the feature flag. Supported in GitLab 13.3 and later' optional :description, type: String, desc: 'The description of the feature flag' - optional :active, type: Boolean, desc: 'Active/inactive value of the flag' + optional :active, type: Boolean, desc: 'The active state of the flag. Supported in GitLab 13.3 and later' optional :strategies, type: Array do - optional :id, type: Integer, desc: 'The strategy id' - optional :name, type: String, desc: 'The strategy type' - optional :parameters, type: JSON, desc: 'The strategy parameters' + optional :id, type: Integer, desc: 'The feature flag strategy ID' + optional :name, type: String, desc: 'The strategy name' + optional :parameters, type: JSON, desc: 'The strategy parameters as a JSON-formatted string e.g. `{"userIds":"user1"}`', documentation: { type: 'String' } optional :_destroy, type: Boolean, desc: 'Delete the strategy when true' optional :scopes, type: Array do - optional :id, type: Integer, desc: 'The environment scope id' + optional :id, type: Integer, desc: 'The scope id' optional :environment_scope, type: String, desc: 'The environment scope of the scope' optional :_destroy, type: Boolean, desc: 'Delete the scope when true' end @@ -142,8 +163,14 @@ module API end desc 'Delete a feature flag' do - detail 'This feature was introduced in GitLab 12.5' + detail 'Deletes a feature flag. This feature was introduced in GitLab 12.5.' success ::API::Entities::FeatureFlag + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags feature_flags_tags end delete do authorize_destroy_feature_flag! diff --git a/lib/api/feature_flags_user_lists.rb b/lib/api/feature_flags_user_lists.rb index f4771c07260..aed277d28a2 100644 --- a/lib/api/feature_flags_user_lists.rb +++ b/lib/api/feature_flags_user_lists.rb @@ -4,6 +4,8 @@ module API class FeatureFlagsUserLists < ::API::Base include PaginationParams + feature_flags_user_lists_tags = %w[feature_flags_user_lists] + error_formatter :json, -> (message, _backtrace, _options, _env, _original_exception) { message.is_a?(String) ? { message: message }.to_json : message.to_json } @@ -16,16 +18,23 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :feature_flags_user_lists do - desc 'Get all feature flags user lists of a project' do - detail 'This feature was introduced in GitLab 12.10' + desc 'List all feature flag user lists for a project' do + detail 'Gets all feature flag user lists for the requested project. ' \ + 'This feature was introduced in GitLab 12.10.' success ::API::Entities::FeatureFlag::UserList + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags feature_flags_user_lists_tags end params do - optional :search, type: String, desc: 'Returns the list of user lists matching the search critiera' + optional :search, type: String, desc: 'Return user lists matching the search criteria' use :pagination end @@ -35,9 +44,15 @@ module API with: ::API::Entities::FeatureFlag::UserList end - desc 'Create a feature flags user list for a project' do - detail 'This feature was introduced in GitLab 12.10' + desc 'Create a feature flag user list' do + detail 'Creates a feature flag user list. This feature was introduced in GitLab 12.10.' success ::API::Entities::FeatureFlag::UserList + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags feature_flags_user_lists_tags end params do requires :name, type: String, desc: 'The name of the list' @@ -59,12 +74,17 @@ module API end params do - requires :iid, type: String, desc: 'The internal ID of the user list' + requires :iid, types: [String, Integer], desc: "The internal ID of the project's feature flag user list" end resource 'feature_flags_user_lists/:iid' do - desc 'Get a single feature flag user list belonging to a project' do - detail 'This feature was introduced in GitLab 12.10' + desc 'Get a feature flag user list' do + detail 'Gets a feature flag user list. This feature was introduced in GitLab 12.10.' success ::API::Entities::FeatureFlag::UserList + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags feature_flags_user_lists_tags end get do present user_project.operations_feature_flags_user_lists.find_by_iid!(params[:iid]), @@ -72,8 +92,14 @@ module API end desc 'Update a feature flag user list' do - detail 'This feature was introduced in GitLab 12.10' + detail 'Updates a feature flag user list. This feature was introduced in GitLab 12.10.' success ::API::Entities::FeatureFlag::UserList + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags feature_flags_user_lists_tags end params do optional :name, type: String, desc: 'The name of the list' @@ -93,8 +119,14 @@ module API end end - desc 'Delete a feature flag user list' do - detail 'This feature was introduced in GitLab 12.10' + desc 'Delete feature flag user list' do + detail 'Deletes a feature flag user list. This feature was introduced in GitLab 12.10.' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Conflict' } + ] + tags feature_flags_user_lists_tags end delete do # TODO: Move the business logic to a service class in app/services/feature_flags. diff --git a/lib/api/features.rb b/lib/api/features.rb index 9d4e6eee82c..6b6f5cbfb3f 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -4,6 +4,8 @@ module API class Features < ::API::Base before { authenticated_as_admin! } + features_tags = %w[features] + feature_category :feature_flags urgency :low @@ -44,8 +46,11 @@ module API end resource :features do - desc 'Get a list of all features' do + desc 'List all features' do + detail 'Get a list of all persisted features, with its gate values.' success Entities::Feature + is_array true + tags features_tags end get do features = Feature.all @@ -53,8 +58,11 @@ module API present features, with: Entities::Feature, current_user: current_user end - desc 'Get a list of all feature definitions' do + desc 'List all feature definitions' do + detail 'Get a list of all feature definitions.' success Entities::Feature::Definition + is_array true + tags features_tags end get :definitions do definitions = ::Feature::Definition.definitions.values.map(&:to_h) @@ -62,30 +70,44 @@ module API present definitions, with: Entities::Feature::Definition, current_user: current_user end - desc 'Set the gate value for the given feature' do + desc 'Set or create a feature' do + detail "Set a feature's gate value. If a feature with the given name doesn't exist yet, it's created. " \ + "The value can be a boolean, or an integer to indicate percentage of time." success Entities::Feature + failure [ + { code: 400, message: 'Bad request' } + ] + tags features_tags end params do - requires :value, type: String, desc: '`true` or `false` to enable/disable, a float for percentage of time' - optional :key, type: String, desc: '`percentage_of_actors` or the default `percentage_of_time`' + requires :value, + types: [String, Integer], + desc: '`true` or `false` to enable/disable, or an integer for percentage of time' + optional :key, type: String, desc: '`percentage_of_actors` or `percentage_of_time` (default)' optional :feature_group, type: String, desc: 'A Feature group name' optional :user, type: String, desc: 'A GitLab username or comma-separated multiple usernames' optional :group, type: String, - desc: "A GitLab group's path, such as 'gitlab-org', or comma-separated multiple group paths" + desc: "A GitLab group's path, for example `gitlab-org`, or comma-separated multiple group paths" optional :namespace, type: String, - desc: "A GitLab group or user namespace path, such as 'john-doe', or comma-separated multiple namespace paths" + desc: "A GitLab group or user namespace's path, for example `john-doe`, or comma-separated " \ + "multiple namespace paths. Introduced in GitLab 15.0." optional :project, type: String, - desc: "A projects path, such as `gitlab-org/gitlab-ce`, or comma-separated multiple project paths" - optional :force, type: Boolean, desc: 'Skip feature flag validation checks, ie. YAML definition' + desc: "A projects path, for example `gitlab-org/gitlab-foss`, or comma-separated multiple project paths" + optional :repository, + type: String, + desc: "A repository path, for example `gitlab-org/gitlab-test.git`, `gitlab-org/gitlab-test.wiki.git`, " \ + "`snippets/21.git`, to name a few. Use comma to separate multiple repository paths" + optional :force, type: Boolean, desc: 'Skip feature flag validation checks, such as a YAML definition' mutually_exclusive :key, :feature_group mutually_exclusive :key, :user mutually_exclusive :key, :group mutually_exclusive :key, :namespace mutually_exclusive :key, :project + mutually_exclusive :key, :repository end post ':name' do if Feature.enabled?(:set_feature_flag_service) @@ -135,7 +157,10 @@ module API bad_request!(e.message) end - desc 'Remove the gate value for the given feature' + desc 'Delete a feature' do + detail "Removes a feature gate. Response is equal when the gate exists, or doesn't." + tags features_tags + end delete ':name' do Feature.remove(params[:name]) diff --git a/lib/api/files.rb b/lib/api/files.rb index fd574ca865b..fa749299b9a 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -30,7 +30,7 @@ module API end def assign_file_vars! - authorize! :download_code, user_project + authorize! :read_code, user_project @commit = user_project.commit(params[:ref]) not_found!('Commit') unless @commit @@ -82,33 +82,44 @@ module API end params :simple_file_params do - requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' - requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.', allow_blank: false - requires :commit_message, type: String, allow_blank: false, desc: 'Commit message' - optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from' - optional :author_email, type: String, desc: 'The email of the author' - optional :author_name, type: String, desc: 'The name of the author' + requires :file_path, type: String, file_path: true, + desc: 'The url encoded path to the file.', documentation: { example: 'lib%2Fclass%2Erb' } + requires :branch, type: String, + desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.', allow_blank: false, + documentation: { example: 'main' } + requires :commit_message, type: String, + allow_blank: false, desc: 'Commit message', documentation: { example: 'Initial commit' } + optional :start_branch, type: String, + desc: 'Name of the branch to start the new commit from', documentation: { example: 'main' } + optional :author_email, type: String, + desc: 'The email of the author', documentation: { example: 'johndoe@example.com' } + optional :author_name, type: String, + desc: 'The name of the author', documentation: { example: 'John Doe' } end params :extended_file_params do use :simple_file_params - requires :content, type: String, desc: 'File content' - optional :encoding, type: String, values: %w[base64], desc: 'File encoding' - optional :last_commit_id, type: String, desc: 'Last known commit id for this file' + requires :content, type: String, desc: 'File content', documentation: { example: 'file content' } + optional :encoding, type: String, values: %w[base64 text], default: 'text', desc: 'File encoding' + optional :last_commit_id, type: String, + desc: 'Last known commit id for this file', + documentation: { example: '2695effb5807a22ff3d138d593fd856244e155e7' } optional :execute_filemode, type: Boolean, desc: 'Enable / Disable the executable flag on the file path' end end params do - requires :id, type: String, desc: 'The project ID' + requires :id, type: String, desc: 'The project ID', documentation: { example: 'gitlab-org/gitlab' } end resource :projects, requirements: FILE_ENDPOINT_REQUIREMENTS do allow_access_with_scope :read_repository, if: -> (request) { request.get? || request.head? } desc 'Get blame file metadata from repository' params do - requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' - requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false + requires :file_path, type: String, file_path: true, + desc: 'The url encoded path to the file.', documentation: { example: 'lib%2Fclass%2Erb' } + requires :ref, type: String, + desc: 'The name of branch, tag or commit', allow_blank: false, documentation: { example: 'main' } end head ":id/repository/files/:file_path/blame", requirements: FILE_ENDPOINT_REQUIREMENTS do assign_file_vars! @@ -118,11 +129,15 @@ module API desc 'Get blame file from the repository' params do - requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' - requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false + requires :file_path, type: String, file_path: true, + desc: 'The url encoded path to the file.', documentation: { example: 'lib%2Fclass%2Erb' } + requires :ref, type: String, + desc: 'The name of branch, tag or commit', allow_blank: false, documentation: { example: 'main' } optional :range, type: Hash do - requires :start, type: Integer, desc: 'The first line of the range to blame', allow_blank: false, values: ->(v) { v > 0 } - requires :end, type: Integer, desc: 'The last line of the range to blame', allow_blank: false, values: ->(v) { v > 0 } + requires :start, type: Integer, + desc: 'The first line of the range to blame', allow_blank: false, values: ->(v) { v > 0 } + requires :end, type: Integer, + desc: 'The last line of the range to blame', allow_blank: false, values: ->(v) { v > 0 } end end get ":id/repository/files/:file_path/blame", requirements: FILE_ENDPOINT_REQUIREMENTS do @@ -138,8 +153,10 @@ module API desc 'Get raw file metadata from repository' params do - requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' - optional :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false + requires :file_path, type: String, file_path: true, + desc: 'The url encoded path to the file.', documentation: { example: 'lib%2Fclass%2Erb' } + optional :ref, type: String, + desc: 'The name of branch, tag or commit', allow_blank: false, documentation: { example: 'main' } end head ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS, urgency: :low do assign_file_vars! @@ -147,10 +164,14 @@ module API set_http_headers(blob_data) end - desc 'Get raw file contents from the repository' + desc 'Get raw file contents from the repository' do + success File + end params do - requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' - optional :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false + requires :file_path, type: String, file_path: true, + desc: 'The url encoded path to the file.', documentation: { example: 'lib%2Fclass%2Erb' } + optional :ref, type: String, + desc: 'The name of branch, tag or commit', allow_blank: false, documentation: { example: 'main' } end get ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS, urgency: :low do assign_file_vars! @@ -163,8 +184,10 @@ module API desc 'Get file metadata from repository' params do - requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' - requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false + requires :file_path, type: String, file_path: true, + desc: 'The url encoded path to the file.', documentation: { example: 'lib%2Fclass%2Erb' } + requires :ref, type: String, + desc: 'The name of branch, tag or commit', allow_blank: false, documentation: { example: 'main' } end head ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS, urgency: :low do assign_file_vars! @@ -174,8 +197,10 @@ module API desc 'Get a file from the repository' params do - requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' - requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false + requires :file_path, type: String, file_path: true, + desc: 'The url encoded path to the file.', documentation: { example: 'lib%2Fclass%2Erb' } + requires :ref, type: String, + desc: 'The name of branch, tag or commit', allow_blank: false, documentation: { example: 'main' } end get ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do assign_file_vars! diff --git a/lib/api/freeze_periods.rb b/lib/api/freeze_periods.rb index e69baeee97f..40f1be83028 100644 --- a/lib/api/freeze_periods.rb +++ b/lib/api/freeze_periods.rb @@ -4,19 +4,28 @@ module API class FreezePeriods < ::API::Base include PaginationParams + freeze_periods_tags = %w[freeze_periods] + before { authenticate! } feature_category :continuous_delivery urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get project freeze periods' do - detail 'This feature was introduced in GitLab 13.0.' + desc 'List freeze periods' do + detail 'Paginated list of Freeze Periods, sorted by created_at in ascending order. ' \ + 'This feature was introduced in GitLab 13.0.' success Entities::FreezePeriod + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags freeze_periods_tags end params do use :pagination @@ -30,12 +39,17 @@ module API present paginate(freeze_periods), with: Entities::FreezePeriod, current_user: current_user end - desc 'Get a single freeze period' do - detail 'This feature was introduced in GitLab 13.0.' + desc 'Get a freeze period' do + detail 'Get a freeze period for the given `freeze_period_id`. This feature was introduced in GitLab 13.0.' success Entities::FreezePeriod + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags freeze_periods_tags end params do - requires :freeze_period_id, type: Integer, desc: 'The ID of a project freeze period' + requires :freeze_period_id, type: Integer, desc: 'The ID of the freeze period' end get ":id/freeze_periods/:freeze_period_id" do authorize! :read_freeze_period, user_project @@ -43,14 +57,21 @@ module API present freeze_period, with: Entities::FreezePeriod, current_user: current_user end - desc 'Create a new freeze period' do - detail 'This feature was introduced in GitLab 13.0.' + desc 'Create a freeze period' do + detail 'Creates a freeze period. This feature was introduced in GitLab 13.0.' success Entities::FreezePeriod + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' } + ] + tags freeze_periods_tags end params do - requires :freeze_start, type: String, desc: 'Freeze Period start' - requires :freeze_end, type: String, desc: 'Freeze Period end' - optional :cron_timezone, type: String, desc: 'Timezone' + requires :freeze_start, type: String, desc: 'Start of the freeze period in cron format.' + requires :freeze_end, type: String, desc: 'End of the freeze period in cron format' + optional :cron_timezone, + type: String, + desc: 'The time zone for the cron fields, defaults to UTC if not provided' end post ':id/freeze_periods' do authorize! :create_freeze_period, user_project @@ -67,13 +88,18 @@ module API end desc 'Update a freeze period' do - detail 'This feature was introduced in GitLab 13.0.' + detail 'Updates a freeze period for the given `freeze_period_id`. This feature was introduced in GitLab 13.0.' success Entities::FreezePeriod + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' } + ] + tags freeze_periods_tags end params do - optional :freeze_start, type: String, desc: 'Freeze Period start' - optional :freeze_end, type: String, desc: 'Freeze Period end' - optional :cron_timezone, type: String, desc: 'Freeze Period Timezone' + optional :freeze_start, type: String, desc: 'Start of the freeze period in cron format' + optional :freeze_end, type: String, desc: 'End of the freeze period in cron format' + optional :cron_timezone, type: String, desc: 'The time zone for the cron fields' end put ':id/freeze_periods/:freeze_period_id' do authorize! :update_freeze_period, user_project @@ -88,11 +114,15 @@ module API end desc 'Delete a freeze period' do - detail 'This feature was introduced in GitLab 13.0.' + detail 'Deletes a freeze period for the given `freeze_period_id`. This feature was introduced in GitLab 13.0.' success Entities::FreezePeriod + failure [ + { code: 401, message: 'Unauthorized' } + ] + tags freeze_periods_tags end params do - requires :freeze_period_id, type: Integer, desc: 'Freeze Period ID' + requires :freeze_period_id, type: Integer, desc: 'The ID of the freeze period' end delete ':id/freeze_periods/:freeze_period_id' do authorize! :destroy_freeze_period, user_project diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb index 0098b074f05..3584f8d025a 100644 --- a/lib/api/generic_packages.rb +++ b/lib/api/generic_packages.rb @@ -18,7 +18,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do @@ -54,7 +54,7 @@ module API requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true optional :status, type: String, values: ALLOWED_STATUSES, desc: 'Package status' - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } optional :select, type: String, values: %w[package_file] end diff --git a/lib/api/geo.rb b/lib/api/geo.rb index cb04d2a4e1e..8798b76b52b 100644 --- a/lib/api/geo.rb +++ b/lib/api/geo.rb @@ -13,6 +13,13 @@ module API end resource :geo do + desc 'Returns a Geo proxy response' do + summary "Determine if a Geo site should proxy requests" + success code: 200 + failure [{ code: 403, message: 'Forbidden' }] + tags %w[geo] + end + # Workhorse calls this to determine if it is a Geo site that should proxy # requests. Workhorse doesn't know if it's in a FOSS/EE context. get '/proxy' do diff --git a/lib/api/go_proxy.rb b/lib/api/go_proxy.rb index 2d9c0cd6ce1..8fde40a4713 100755 --- a/lib/api/go_proxy.rb +++ b/lib/api/go_proxy.rb @@ -4,6 +4,8 @@ module API helpers Gitlab::Golang helpers ::API::Helpers::PackagesHelpers + GO_PROXY_TAGS = %w[go_proxy].freeze + feature_category :package_registry urgency :low @@ -17,6 +19,10 @@ module API before { require_packages_enabled! } helpers do + def project + user_project(action: :read_package) + end + def case_decode(str) # Converts "github.com/!azure" to "github.com/Azure" # @@ -32,12 +38,12 @@ module API end def find_module - not_found! unless Feature.enabled?(:go_proxy, user_project) + not_found! unless Feature.enabled?(:go_proxy, project) module_name = case_decode params[:module_name] bad_request_missing_attribute!('Module Name') if module_name.blank? - mod = ::Packages::Go::ModuleFinder.new(user_project, module_name).execute + mod = ::Packages::Go::ModuleFinder.new(project, module_name).execute not_found! unless mod @@ -58,18 +64,21 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' - requires :module_name, type: String, desc: 'Module name', coerce_with: ->(val) { CGI.unescape(val) } + requires :id, types: [String, Integer], desc: 'The project ID or full path of a project' + requires :module_name, type: String, desc: 'The name of the Go module', coerce_with: ->(val) { CGI.unescape(val) } end - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, authenticate_non_public: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, + authenticate_non_public: true resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before do - authorize_read_package! + authorize_read_package!(project) end namespace ':id/packages/go/*module_name/@v' do - desc 'Get all tagged versions for a given Go module' do - detail 'See `go help goproxy`, GET $GOPROXY//@v/list. This feature was introduced in GitLab 13.1.' + desc 'List' do + detail 'Get all tagged versions for a given Go module.'\ + 'See `go help goproxy`, GET $GOPROXY//@v/list. This feature was introduced in GitLab 13.1.' + tags GO_PROXY_TAGS end get 'list' do mod = find_module @@ -78,12 +87,14 @@ module API mod.versions.map { |t| t.name }.join("\n") end - desc 'Get information about the given module version' do - detail 'See `go help goproxy`, GET $GOPROXY//@v/.info. This feature was introduced in GitLab 13.1.' + desc 'Version metadata' do + detail 'Get all tagged versions for a given Go module.'\ + 'See `go help goproxy`, GET $GOPROXY//@v/.info. This feature was introduced in GitLab 13.1' success ::API::Entities::GoModuleVersion + tags GO_PROXY_TAGS end params do - requires :module_version, type: String, desc: 'Module version' + requires :module_version, type: String, desc: 'The version of the Go module' end get ':module_version.info', requirements: MODULE_VERSION_REQUIREMENTS do ver = find_version @@ -91,11 +102,13 @@ module API present ::Packages::Go::ModuleVersionPresenter.new(ver), with: ::API::Entities::GoModuleVersion end - desc 'Get the module file of the given module version' do - detail 'See `go help goproxy`, GET $GOPROXY//@v/.mod. This feature was introduced in GitLab 13.1.' + desc 'Download module file' do + detail 'Get the module file of a given module version.'\ + 'See `go help goproxy`, GET $GOPROXY//@v/.mod. This feature was introduced in GitLab 13.1.' + tags GO_PROXY_TAGS end params do - requires :module_version, type: String, desc: 'Module version' + requires :module_version, type: String, desc: 'The version of the Go module' end get ':module_version.mod', requirements: MODULE_VERSION_REQUIREMENTS do ver = find_version @@ -104,18 +117,21 @@ module API ver.gomod end - desc 'Get a zip of the source of the given module version' do - detail 'See `go help goproxy`, GET $GOPROXY//@v/.zip. This feature was introduced in GitLab 13.1.' + desc 'Download module source' do + detail 'Get a zip of the source of the given module version.'\ + 'See `go help goproxy`, GET $GOPROXY//@v/.zip. This feature was introduced in GitLab 13.1.' + tags GO_PROXY_TAGS end params do - requires :module_version, type: String, desc: 'Module version' + requires :module_version, type: String, desc: 'The version of the Go module' end get ':module_version.zip', requirements: MODULE_VERSION_REQUIREMENTS do ver = find_version content_type 'application/zip' env['api.format'] = :binary - header['Content-Disposition'] = ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: ver.name + '.zip') + header['Content-Disposition'] = + ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: "#{ver.name}.zip") header['Content-Transfer-Encoding'] = 'binary' status :ok body ver.archive.string diff --git a/lib/api/group_avatar.rb b/lib/api/group_avatar.rb index 9063040c763..0820011fd89 100644 --- a/lib/api/group_avatar.rb +++ b/lib/api/group_avatar.rb @@ -7,11 +7,13 @@ module API feature_category :subgroups params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, type: String, desc: 'The ID of the group' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Download the group avatar' do detail 'This feature was introduced in GitLab 14.0' + tags %w[group_avatar] + success code: 200 end get ':id/avatar' do avatar = user_group.avatar diff --git a/lib/api/group_clusters.rb b/lib/api/group_clusters.rb index edaa32c26c4..de5ca0f86ae 100644 --- a/lib/api/group_clusters.rb +++ b/lib/api/group_clusters.rb @@ -16,8 +16,14 @@ module API requires :id, type: String, desc: 'The ID of the group' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get all clusters from the group' do + desc 'List group clusters' do + detail 'This feature was introduced in GitLab 12.1. Returns a list of group clusters.' success Entities::Cluster + failure [ + { code: 403, message: 'Forbidden' } + ] + is_array true + tags %w[clusters] end params do use :pagination @@ -28,8 +34,14 @@ module API present paginate(clusters_for_current_user), with: Entities::Cluster end - desc 'Get specific cluster for the group' do + desc 'Get a single group cluster' do + detail 'This feature was introduced in GitLab 12.1. Gets a single group cluster.' success Entities::ClusterGroup + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :cluster_id, type: Integer, desc: 'The cluster ID' @@ -40,8 +52,15 @@ module API present cluster, with: Entities::ClusterGroup end - desc 'Adds an existing cluster' do + desc 'Add existing cluster to group' do + detail 'This feature was introduced in GitLab 12.1. Adds an existing Kubernetes cluster to the group.' success Entities::ClusterGroup + failure [ + { code: 400, message: 'Validation error' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :name, type: String, desc: 'Cluster name' @@ -73,8 +92,15 @@ module API end end - desc 'Update an existing cluster' do + desc 'Edit group cluster' do + detail 'This feature was introduced in GitLab 12.1. Updates an existing group cluster.' success Entities::ClusterGroup + failure [ + { code: 400, message: 'Validation error' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :cluster_id, type: Integer, desc: 'The cluster ID' @@ -104,8 +130,14 @@ module API end end - desc 'Remove a cluster' do + desc 'Delete group cluster' do + detail 'This feature was introduced in GitLab 12.1. Deletes an existing group cluster. Does not remove existing resources within the connected Kubernetes cluster.' success Entities::ClusterGroup + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :cluster_id, type: Integer, desc: 'The Cluster ID' diff --git a/lib/api/group_container_repositories.rb b/lib/api/group_container_repositories.rb index b834d177a12..753f0db10c1 100644 --- a/lib/api/group_container_repositories.rb +++ b/lib/api/group_container_repositories.rb @@ -16,12 +16,19 @@ module API tag_name: API::NO_SLASH_URL_PART_REGEX) params do - requires :id, type: String, desc: "Group's ID or path" + requires :id, types: [String, Integer], + desc: 'The ID or URL-encoded path of the group accessible by the authenticated user' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get a list of all repositories within a group' do - detail 'This feature was introduced in GitLab 12.2.' + desc 'List registry repositories within a group' do + detail 'Get a list of registry repositories in a group. This feature was introduced in GitLab 12.2.' success Entities::ContainerRegistry::Repository + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Group Not Found' } + ] + is_array true + tags %w[container_registry] end params do use :pagination diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb index 2948960a9b4..eb0a01e0d3d 100644 --- a/lib/api/group_export.rb +++ b/lib/api/group_export.rb @@ -15,6 +15,16 @@ module API resource :groups, requirements: { id: %r{[^/]+} } do desc 'Download export' do detail 'This feature was introduced in GitLab 12.5.' + tags %w[group_export] + produces %w[application/octet-stream application/json] + success code: 200 + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] end get ':id/export/download' do check_rate_limit! :group_download_export, scope: [current_user, user_group] @@ -32,6 +42,15 @@ module API desc 'Start export' do detail 'This feature was introduced in GitLab 12.5.' + tags %w[group_export] + success code: 202 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 429, message: 'Too many requests' }, + { code: 503, message: 'Service unavailable' } + ] end post ':id/export' do check_rate_limit! :group_export, scope: current_user @@ -47,6 +66,14 @@ module API desc 'Start relations export' do detail 'This feature was introduced in GitLab 13.12' + tags %w[group_export] + success code: 202 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] end post ':id/export_relations' do response = ::BulkImports::ExportService.new(portable: user_group, user: current_user).execute @@ -60,6 +87,15 @@ module API desc 'Download relations export' do detail 'This feature was introduced in GitLab 13.12' + produces %w[application/octet-stream application/json] + tags %w[group_export] + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] end params do requires :relation, type: String, desc: 'Group relation name' @@ -77,6 +113,15 @@ module API desc 'Relations export status' do detail 'This feature was introduced in GitLab 13.12' + is_array true + tags %w[group_export] + success code: 200, model: Entities::BulkImports::ExportStatus + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] end get ':id/export_relations/status' do present user_group.bulk_import_exports, with: Entities::BulkImports::ExportStatus diff --git a/lib/api/group_import.rb b/lib/api/group_import.rb index cef9b542c9e..609a7ed0ef0 100644 --- a/lib/api/group_import.rb +++ b/lib/api/group_import.rb @@ -32,6 +32,7 @@ module API resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Workhorse authorize the group import upload' do detail 'This feature was introduced in GitLab 12.8' + tags ['group_import'] end post 'import/authorize' do require_gitlab_workhorse! @@ -49,7 +50,15 @@ module API desc 'Create a new group import' do detail 'This feature was introduced in GitLab 12.8' - success Entities::Group + success code: 202 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 400, message: 'Bad request' }, + { code: 503, message: 'Service unavailable' } + ] + consumes ['multipart/form-data'] + tags ['group_import'] end params do requires :path, type: String, desc: 'Group path' diff --git a/lib/api/group_packages.rb b/lib/api/group_packages.rb index 72d67b41c31..c2b4cbf732f 100644 --- a/lib/api/group_packages.rb +++ b/lib/api/group_packages.rb @@ -14,13 +14,19 @@ module API helpers ::API::Helpers::PackagesHelpers params do - requires :id, type: String, desc: "Group's ID or path" + requires :id, types: [String, Integer], desc: 'ID or URL-encoded path of the group' optional :exclude_subgroups, type: Boolean, default: false, desc: 'Determines if subgroups should be excluded' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get all project packages within a group' do - detail 'This feature was introduced in GitLab 12.5' + desc 'List packages within a group' do + detail 'Get a list of project packages at the group level. This feature was introduced in GitLab 12.5' success ::API::Entities::Package + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Group Not Found' } + ] + is_array true + tags %w[group_packages] end params do use :pagination @@ -53,10 +59,13 @@ module API packages = Packages::GroupPackagesFinder.new( current_user, user_group, - declared(params).slice(:exclude_subgroups, :order_by, :sort, :package_type, :package_name, :include_versionless, :status) + declared(params).slice( + :exclude_subgroups, :order_by, :sort, :package_type, :package_name, :include_versionless, :status + ) ).execute - present paginate(packages), with: ::API::Entities::Package, user: current_user, group: true, namespace: user_group + present paginate(packages), with: ::API::Entities::Package, user: current_user, group: true, + namespace: user_group end end end diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb index 2235746b254..a42f9045b9d 100644 --- a/lib/api/group_variables.rb +++ b/lib/api/group_variables.rb @@ -11,12 +11,14 @@ module API helpers ::API::Helpers::VariablesHelpers params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, type: String, desc: 'The ID of a group or URL-encoded path of the group owned by the authenticated + user' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get group-level variables' do + desc 'Get a list of group-level variables' do success Entities::Ci::Variable + tags %w[ci_variables] end params do use :pagination @@ -26,8 +28,10 @@ module API present paginate(variables), with: Entities::Ci::Variable end - desc 'Get a specific variable from a group' do + desc 'Get the details of a group’s specific variable' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Group Variable Not Found' }] + tags %w[ci_variables] end params do requires :key, type: String, desc: 'The key of the variable' @@ -42,14 +46,19 @@ module API desc 'Create a new variable in a group' do success Entities::Ci::Variable + failure [{ code: 400, message: '400 Bad Request' }] + tags %w[ci_variables] end + route_setting :log_safety, { safe: %w[key], unsafe: %w[value] } params do - requires :key, type: String, desc: 'The key of the variable' - requires :value, type: String, desc: 'The value of the variable' + requires :key, type: String, desc: 'The ID of a group or URL-encoded path of the group owned by the + authenticated user' + requires :value, type: String, desc: 'The value of a variable' optional :protected, type: String, desc: 'Whether the variable is protected' optional :masked, type: String, desc: 'Whether the variable is masked' - optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' - + optional :raw, type: String, desc: 'Whether the variable will be expanded' + optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of the variable. Default: env_var' + optional :environment_scope, type: String, desc: 'The environment scope of a variable' use :optional_group_variable_params_ee end post ':id/variables' do @@ -73,13 +82,18 @@ module API desc 'Update an existing variable from a group' do success Entities::Ci::Variable + failure [{ code: 400, message: '400 Bad Request' }, { code: 404, message: 'Group Variable Not Found' }] + tags %w[ci_variables] end + route_setting :log_safety, { safe: %w[key], unsafe: %w[value] } params do - optional :key, type: String, desc: 'The key of the variable' - optional :value, type: String, desc: 'The value of the variable' + optional :key, type: String, desc: 'The key of a variable' + optional :value, type: String, desc: 'The value of a variable' optional :protected, type: String, desc: 'Whether the variable is protected' optional :masked, type: String, desc: 'Whether the variable is masked' - optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file' + optional :raw, type: String, desc: 'Whether the variable will be expanded' + optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of the variable. Default: env_var' + optional :environment_scope, type: String, desc: 'The environment scope of a variable' use :optional_group_variable_params_ee end @@ -106,9 +120,11 @@ module API desc 'Delete an existing variable from a group' do success Entities::Ci::Variable + failure [{ code: 404, message: 'Group Variable Not Found' }] + tags %w[ci_variables] end params do - requires :key, type: String, desc: 'The key of the variable' + requires :key, type: String, desc: 'The key of a variable' end delete ':id/variables/:key' do variable = find_variable(user_group, params) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 0eb4fbb196c..75e7612bd5b 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -592,19 +592,19 @@ module API end end - def present_artifacts_file!(file, project:, **args) + def present_artifacts_file!(file, **args) log_artifacts_filesize(file&.model) - present_carrierwave_file!(file, project: project, **args) + present_carrierwave_file!(file, **args) end - def present_carrierwave_file!(file, project: nil, supports_direct_download: true) + def present_carrierwave_file!(file, supports_direct_download: true) return not_found! unless file&.exists? if file.file_storage? present_disk_file!(file.path, file.filename) elsif supports_direct_download && file.class.direct_download_enabled? - redirect(cdn_fronted_url(file, project)) + redirect(cdn_fronted_url(file)) else header(*Gitlab::Workhorse.send_url(file.url)) status :ok @@ -612,9 +612,9 @@ module API end end - def cdn_fronted_url(file, project) + def cdn_fronted_url(file) if file.respond_to?(:cdn_enabled_url) - result = file.cdn_enabled_url(project, ip_address) + result = file.cdn_enabled_url(ip_address) Gitlab::ApplicationContext.push(artifact_used_cdn: result.used_cdn) result.url else @@ -673,7 +673,6 @@ module API finder_params[:with_issues_enabled] = true if params[:with_issues_enabled].present? finder_params[:with_merge_requests_enabled] = true if params[:with_merge_requests_enabled].present? - finder_params[:without_deleted] = true finder_params[:search_namespaces] = true if params[:search_namespaces].present? finder_params[:user] = params.delete(:user) if params[:user] finder_params[:id_after] = sanitize_id_param(params[:id_after]) if params[:id_after] diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index e03f029a6ef..56db6ee4c5c 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -124,7 +124,12 @@ module API repository: repository.gitaly_repository.to_h, address: Gitlab::GitalyClient.address(repository.shard), token: Gitlab::GitalyClient.token(repository.shard), - features: Feature::Gitaly.server_feature_flags(repository.project) + features: Feature::Gitaly.server_feature_flags( + user: ::Feature::Gitaly.user_actor(actor.user), + repository: repository, + project: ::Feature::Gitaly.project_actor(repository.container), + group: ::Feature::Gitaly.group_actor(repository.container) + ) } end end diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb index 8572cc89e71..b3ba962666f 100644 --- a/lib/api/helpers/label_helpers.rb +++ b/lib/api/helpers/label_helpers.rb @@ -137,9 +137,10 @@ module API end def create_service_params(parent) - if parent.is_a?(Project) + case parent + when Project { project: parent } - elsif parent.is_a?(Group) + when Group { group: parent } else raise TypeError, 'Parent type is not supported' diff --git a/lib/api/helpers/merge_requests_helpers.rb b/lib/api/helpers/merge_requests_helpers.rb index 85648cd166d..eed9fa30d3c 100644 --- a/lib/api/helpers/merge_requests_helpers.rb +++ b/lib/api/helpers/merge_requests_helpers.rb @@ -8,6 +8,9 @@ module API UNPROCESSABLE_ERROR_KEYS = [:project_access, :branch_conflict, :validate_fork, :base].freeze + params :ee_approval_params do + end + params :merge_requests_negatable_params do optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID' optional :author_username, type: String, desc: 'Return merge requests which are authored by the user with the given username' @@ -136,3 +139,5 @@ module API end end end + +API::Helpers::MergeRequestsHelpers.prepend_mod_with('API::Helpers::MergeRequestsHelpers') diff --git a/lib/api/helpers/packages/dependency_proxy_helpers.rb b/lib/api/helpers/packages/dependency_proxy_helpers.rb index dc81e5e1b51..1ae863a5a25 100644 --- a/lib/api/helpers/packages/dependency_proxy_helpers.rb +++ b/lib/api/helpers/packages/dependency_proxy_helpers.rb @@ -45,7 +45,7 @@ module API raise ArgumentError, "Can't find application setting for package_type #{package_type}" unless application_setting_name - if target.present? && Feature.enabled?(:cascade_package_forwarding_settings, target) + if target.present? target.public_send(application_setting_name) # rubocop:disable GitlabSecurity/PublicSend else ::Gitlab::CurrentSettings diff --git a/lib/api/helpers/packages/npm.rb b/lib/api/helpers/packages/npm.rb index 34e126c73fc..352d77f472c 100644 --- a/lib/api/helpers/packages/npm.rb +++ b/lib/api/helpers/packages/npm.rb @@ -19,7 +19,7 @@ module API strong_memoize(:project) do case endpoint_scope when :project - user_project + user_project(action: :read_package) when :instance # Simulate the same behavior as #user_project by re-using #find_project! # but take care if the project_id is nil as #find_project! is not designed diff --git a/lib/api/helpers/packages_helpers.rb b/lib/api/helpers/packages_helpers.rb index 687c8330cc8..96a10d43401 100644 --- a/lib/api/helpers/packages_helpers.rb +++ b/lib/api/helpers/packages_helpers.rb @@ -3,6 +3,8 @@ module API module Helpers module PackagesHelpers + extend ::Gitlab::Utils::Override + MAX_PACKAGE_FILE_SIZE = 50.megabytes.freeze def require_packages_enabled! @@ -48,6 +50,34 @@ module API require_gitlab_workhorse! end + override :user_project + def user_project(action: :read_project) + case action + when :read_project + super() + when :read_package + user_project_with_read_package + else + raise ArgumentError, "unexpected action: #{action}" + end + end + + # This function is similar to the `find_project!` function, but it considers the `read_package` ability. + def user_project_with_read_package + strong_memoize(:user_project_with_read_package) do + project = find_project(params[:id]) + + next forbidden! unless authorized_project_scope?(project) + + next project if can?(current_user, :read_package, project&.packages_policy_subject) + # guest users can have :read_project but not :read_package + next forbidden! if can?(current_user, :read_project, project) + next unauthorized! if authenticate_non_public? + + not_found!('Project') + end + end + def track_package_event(event_name, scope, **args) ::Packages::CreateEventService.new(nil, current_user, event_name: event_name, scope: scope).execute category = args.delete(:category) || self.options[:for].name diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 9839828a5b4..c95bf0f0c21 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -65,6 +65,7 @@ module API optional :suggestion_commit_message, type: String, desc: 'The commit message used to apply merge request suggestions' optional :merge_commit_template, type: String, desc: 'Template used to create merge commit message' optional :squash_commit_template, type: String, desc: 'Template used to create squash commit message' + optional :issue_branch_template, type: String, desc: 'Template used to create a branch from an issue' optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md" optional :ci_default_git_depth, type: Integer, desc: 'Default number of revisions for shallow cloning' optional :auto_devops_enabled, type: Boolean, desc: 'Flag indication if Auto DevOps is enabled' @@ -96,7 +97,7 @@ module API end params :optional_update_params_ce do - optional :ci_forward_deployment_enabled, type: Boolean, desc: 'Skip older deployment jobs that are still pending' + optional :ci_forward_deployment_enabled, type: Boolean, desc: 'Prevent older deployment jobs that are still pending' optional :ci_allow_fork_pipelines_to_run_in_parent_project, type: Boolean, desc: 'Allow fork merge request pipelines to run in parent project' optional :ci_separated_caches, type: Boolean, desc: 'Enable or disable separated caches based on branch protection.' optional :restrict_user_defined_variables, type: Boolean, desc: 'Restrict use of user-defined variables when triggering a pipeline' @@ -174,6 +175,7 @@ module API :suggestion_commit_message, :merge_commit_template, :squash_commit_template, + :issue_branch_template, :repository_storage, :packages_enabled, :service_desk_enabled, diff --git a/lib/api/helpers/users_helpers.rb b/lib/api/helpers/users_helpers.rb index 1a019283bc6..e80b89488a2 100644 --- a/lib/api/helpers/users_helpers.rb +++ b/lib/api/helpers/users_helpers.rb @@ -18,6 +18,13 @@ module API error_messages[:bio] = error_messages.delete(:"user_detail.bio") if error_messages.has_key?(:"user_detail.bio") end end + + # rubocop: disable CodeReuse/ActiveRecord + def find_user_by_id(params) + id = params[:user_id] || params[:id] + User.find_by(id: id) || not_found!('User') + end + # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/api/helpers/web_hooks_helpers.rb b/lib/api/helpers/web_hooks_helpers.rb index a71e56af4c3..5d5067bc70e 100644 --- a/lib/api/helpers/web_hooks_helpers.rb +++ b/lib/api/helpers/web_hooks_helpers.rb @@ -6,7 +6,7 @@ module API extend Grape::API::Helpers params :requires_url do - requires :url, type: String, desc: "The URL to send the request to" + requires :url, type: String, desc: "The URL to send the request to", documentation: { example: 'http://example.com/hook' } end params :optional_url do @@ -15,8 +15,8 @@ module API params :url_variables do optional :url_variables, type: Array, desc: 'URL variables for interpolation' do - requires :key, type: String, desc: 'Name of the variable' - requires :value, type: String, desc: 'Value of the variable' + requires :key, type: String, desc: 'Name of the variable', documentation: { example: 'token' } + requires :value, type: String, desc: 'Value of the variable', documentation: { example: '123' } end end diff --git a/lib/api/import_bitbucket_server.rb b/lib/api/import_bitbucket_server.rb index 0f2d6239d0d..f315ae5afff 100644 --- a/lib/api/import_bitbucket_server.rb +++ b/lib/api/import_bitbucket_server.rb @@ -22,6 +22,14 @@ module API desc 'Import a BitBucket Server repository' do detail 'This feature was introduced in GitLab 13.2.' success ::ProjectEntity + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 422, message: 'Unprocessable entity' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_import_bitbucket'] end params do diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb index 493cc038f46..d742e3732a8 100644 --- a/lib/api/import_github.rb +++ b/lib/api/import_github.rb @@ -2,6 +2,8 @@ module API class ImportGithub < ::API::Base + before { authenticate! } + feature_category :importers urgency :low @@ -35,7 +37,15 @@ module API desc 'Import a GitHub project' do detail 'This feature was introduced in GitLab 11.3.4.' - success ::ProjectEntity + success code: 201, model: ::ProjectEntity + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 422, message: 'Unprocessable entity' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_import_github'] end params do requires :personal_access_token, type: String, desc: 'GitHub personal access token' @@ -56,6 +66,18 @@ module API end end + desc 'Cancel GitHub project import' do + detail 'This feature was introduced in GitLab 15.5' + success code: 200, model: ProjectImportEntity + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_import_github'] + end params do requires :project_id, type: Integer, desc: 'ID of importing project to be canceled' end diff --git a/lib/api/integrations.rb b/lib/api/integrations.rb index 71c55704ddf..408fa038b0d 100644 --- a/lib/api/integrations.rb +++ b/lib/api/integrations.rb @@ -3,6 +3,8 @@ module API class Integrations < ::API::Base feature_category :integrations + INTEGRATIONS_TAGS = %w[integrations].freeze + integrations = Helpers::IntegrationsHelpers.integrations integration_classes = Helpers::IntegrationsHelpers.integration_classes @@ -65,14 +67,21 @@ module API # The support for `:id/services` can be dropped if we create an API V5. [':id/services', ':id/integrations'].each do |path| params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before { authenticate! } before { authorize_admin_project } - desc 'Get all active project integrations' do + desc 'List all active integrations' do + detail 'Get a list of all active project integrations.' success Entities::ProjectIntegrationBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags INTEGRATIONS_TAGS end get path do integrations = user_project.integrations.active @@ -81,7 +90,16 @@ module API end INTEGRATIONS.each do |slug, settings| - desc "Set #{slug} integration for project" + desc "Create/Edit #{slug.titleize} integration" do + detail "Set #{slug.titleize} integration for a project." + success Entities::ProjectIntegrationBasic + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags INTEGRATIONS_TAGS + end params do settings.each do |setting| if setting[:required] @@ -103,7 +121,16 @@ module API end end - desc "Delete an integration from a project" + desc "Disable an integration" do + detail "Disable the integration for a project. Integration settings are preserved." + success code: 204 + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags INTEGRATIONS_TAGS + end params do requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration' end @@ -124,8 +151,15 @@ module API end end - desc 'Get the integration settings for a project' do + desc "Get an integration settings" do + detail "Get the integration settings for a project." success Entities::ProjectIntegration + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags INTEGRATIONS_TAGS end params do requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration' @@ -149,11 +183,16 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc "Trigger a slash command for #{integration_slug}" do detail 'Added in GitLab 8.13' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags INTEGRATIONS_TAGS end params do settings.each do |setting| diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index 6f964d5636b..d06d1e9862a 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -61,15 +61,6 @@ module API Guest.can?(:download_code, project) || agent.has_access_to?(project) end - def count_events - strong_memoize(:count_events) do - events = params.slice(:gitops_sync_count, :k8s_api_proxy_request_count) - events.transform_keys! { |event| event.to_s.chomp('_count') } - events = params[:counters]&.slice(:gitops_sync, :k8s_api_proxy_request) unless events.present? - events - end - end - def increment_unique_events events = params[:unique_counters]&.slice(:agent_users_using_ci_tunnel) @@ -77,6 +68,12 @@ module API increment_unique_values(event, entity_ids) end end + + def increment_count_events + events = params[:counters]&.slice(:gitops_sync, :k8s_api_proxy_request) + + Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(events) + end end namespace 'internal' do @@ -144,26 +141,17 @@ module API detail 'Updates usage metrics for agent' end params do - # Todo: Remove gitops_sync_count and k8s_api_proxy_request_count in the next milestone - # https://gitlab.com/gitlab-org/gitlab/-/issues/369489 - # We're only keeping it for backwards compatibility until KAS is released - # using `counts:` instead - optional :gitops_sync_count, type: Integer, desc: 'The count to increment the gitops_sync metric by' - optional :k8s_api_proxy_request_count, type: Integer, desc: 'The count to increment the k8s_api_proxy_request_count metric by' optional :counters, type: Hash do optional :gitops_sync, type: Integer, desc: 'The count to increment the gitops_sync metric by' - optional :k8s_api_proxy_request, type: Integer, desc: 'The count to increment the k8s_api_proxy_request_count metric by' + optional :k8s_api_proxy_request, type: Integer, desc: 'The count to increment the k8s_api_proxy_request metric by' end - mutually_exclusive :counters, :gitops_sync_count - mutually_exclusive :counters, :k8s_api_proxy_request_count optional :unique_counters, type: Hash do optional :agent_users_using_ci_tunnel, type: Set[Integer], desc: 'A set of user ids that have interacted a CI Tunnel to' end end post '/' do - Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(count_events) if count_events - + increment_count_events increment_unique_events no_content! diff --git a/lib/api/internal/pages.rb b/lib/api/internal/pages.rb index 6be2679af14..771059053ac 100644 --- a/lib/api/internal/pages.rb +++ b/lib/api/internal/pages.rb @@ -5,6 +5,7 @@ module API module Internal class Pages < ::API::Base feature_category :pages + urgency :low before do authenticate_gitlab_pages_request! diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb index 6fb3eca0ba8..6aefdf146cf 100644 --- a/lib/api/invitations.rb +++ b/lib/api/invitations.rb @@ -18,11 +18,12 @@ module API desc 'Invite non-members by email address to a group or project.' do detail 'This feature was introduced in GitLab 13.6' success Entities::Invitation + tags %w[invitations] end params do requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)' - optional :email, types: [String, Array[String]], email_or_email_list: true, desc: 'The email address to invite, or multiple emails separated by comma' - optional :user_id, types: [Integer, String], desc: 'The user ID of the new member or multiple IDs separated by commas.' + optional :email, type: Array[String], email_or_email_list: true, coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The email address to invite, or multiple emails separated by comma' + optional :user_id, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The user ID of the new member or multiple IDs separated by commas.' optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'invitations-api' optional :tasks_to_be_done, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Tasks the inviter wants the member to do' @@ -44,8 +45,12 @@ module API desc 'Get a list of group or project invitations viewable by the authenticated user' do detail 'This feature was introduced in GitLab 13.6' success Entities::Invitation + is_array true + tags %w[invitations] end params do + optional :page, type: Integer, desc: 'Page to retrieve' + optional :per_page, type: Integer, desc: 'Number of member invitations to return per page' optional :query, type: String, desc: 'A query string to search for members' use :pagination end @@ -62,6 +67,7 @@ module API desc 'Updates a group or project invitation.' do success Entities::Member + tags %w[invitations] end params do requires :email, type: String, desc: 'The email address of the invitation' @@ -93,7 +99,15 @@ module API end end - desc 'Removes an invitation from a group or project.' + desc 'Removes an invitation from a group or project.' do + success code: 204 + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Could not delete invitation' } + ] + tags %w[invitations] + end params do requires :email, type: String, desc: 'The email address of the invitation' end diff --git a/lib/api/issue_links.rb b/lib/api/issue_links.rb index 563fb3358ed..020b02248a0 100644 --- a/lib/api/issue_links.rb +++ b/lib/api/issue_links.rb @@ -6,16 +6,27 @@ module API before { authenticate! } + ISSUE_LINKS_TAGS = %w[issue_links].freeze + feature_category :team_planning urgency :low params do - requires :id, type: String, desc: 'The ID of a project' - requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue' + requires :id, types: [String, Integer], + desc: 'The ID or URL-encoded path of the project owned by the authenticated user' + requires :issue_iid, type: Integer, desc: 'The internal ID of a project’s issue' end resource :projects, requirements: { id: %r{[^/]+} } do - desc 'Get related issues' do + desc 'List issue relations' do + detail 'Get a list of a given issue’s linked issues, sorted by the relationship creation datetime (ascending).'\ + 'Issues are filtered according to the user authorizations.' success Entities::RelatedIssue + is_array true + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags ISSUE_LINKS_TAGS end get ':id/issues/:issue_iid/links' do source_issue = find_project_issue(params[:issue_iid]) @@ -30,14 +41,23 @@ module API include_subscribed: false end - desc 'Relate issues' do + desc 'Create an issue link' do + detail 'Creates a two-way relation between two issues.'\ + 'The user must be allowed to update both issues to succeed.' success Entities::IssueLink + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' } + ] + tags ISSUE_LINKS_TAGS end params do - requires :target_project_id, type: String, desc: 'The ID of the target project' - requires :target_issue_iid, type: Integer, desc: 'The IID of the target issue' + requires :target_project_id, types: [String, Integer], + desc: 'The ID or URL-encoded path of a target project' + requires :target_issue_iid, types: [String, Integer], desc: 'The internal ID of a target project’s issue' optional :link_type, type: String, values: IssueLink.link_types.keys, - desc: 'The type of the relation' + desc: 'The type of the relation (“relates_to”, “blocks”, “is_blocked_by”),'\ + 'defaults to “relates_to”)' end # rubocop: disable CodeReuse/ActiveRecord post ':id/issues/:issue_iid/links' do @@ -61,12 +81,17 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - desc 'Get issues relation' do - detail 'This feature was introduced in GitLab 15.1.' + desc 'Get an issue link' do + detail 'Gets details about an issue link. This feature was introduced in GitLab 15.1.' success Entities::IssueLink + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags ISSUE_LINKS_TAGS end params do - requires :issue_link_id, type: Integer, desc: 'The ID of an issue link' + requires :issue_link_id, types: [String, Integer], desc: 'ID of an issue relationship' end get ':id/issues/:issue_iid/links/:issue_link_id' do issue = find_project_issue(params[:issue_iid]) @@ -77,11 +102,17 @@ module API present issue_link, with: Entities::IssueLink end - desc 'Remove issues relation' do + desc 'Delete an issue link' do + detail 'Deletes an issue link, thus removes the two-way relationship.' success Entities::IssueLink + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags ISSUE_LINKS_TAGS end params do - requires :issue_link_id, type: Integer, desc: 'The ID of an issue link' + requires :issue_link_id, types: [String, Integer], desc: 'The ID of an issue relationship' end delete ':id/issues/:issue_iid/links/:issue_link_id' do issue = find_project_issue(params[:issue_iid]) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index b8b4019765d..b08819e34e3 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -198,7 +198,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do include TimeTrackingEndpoints diff --git a/lib/api/keys.rb b/lib/api/keys.rb index fb1bedd5e92..77952bac01a 100644 --- a/lib/api/keys.rb +++ b/lib/api/keys.rb @@ -9,8 +9,13 @@ module API resource :keys do desc 'Get single ssh key by id. Only available to admin users' do + detail 'Get SSH key with user by ID of an SSH key. Note only administrators can lookup SSH key with user by ID\ + of an SSH key' success Entities::SSHKeyWithUser end + params do + requires :id, types: [String, Integer], desc: 'The ID of an SSH key', documentation: { example: '2' } + end get ":id" do authenticated_as_admin! @@ -19,11 +24,14 @@ module API present key, with: Entities::SSHKeyWithUser, current_user: current_user end - desc 'Get SSH Key information' do + desc 'Get user by fingerprint of SSH key' do success Entities::UserWithAdmin + detail 'You can search for a user that owns a specific SSH key. Note only administrators can lookup SSH key\ + with the fingerprint of an SSH key' end params do - requires :fingerprint, type: String, desc: 'Search for a SSH fingerprint' + requires :fingerprint, type: String, desc: 'The fingerprint of an SSH key', + documentation: { example: 'ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1' } end get do authenticated_with_can_read_all_resources! diff --git a/lib/api/labels.rb b/lib/api/labels.rb index 0a107a96d61..2e00affbbdf 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -15,7 +15,7 @@ module API label_id: API::NO_SLASH_URL_PART_REGEX) params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: LABEL_ENDPOINT_REQUIREMENTS do desc 'Get all labels of the project' do diff --git a/lib/api/lint.rb b/lib/api/lint.rb index f65ecf3b4a6..89787ba00c2 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -15,12 +15,18 @@ module API end namespace :ci do - desc 'Validation of .gitlab-ci.yml content' + desc 'Validates the .gitlab-ci.yml content' do + detail 'Checks if CI/CD YAML configuration is valid' + success code: 200, model: Entities::Ci::Lint::Result + tags %w[ci_lint] + end params do - requires :content, type: String, desc: 'Content of .gitlab-ci.yml' - optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response' - optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response' + requires :content, type: String, desc: 'The CI/CD configuration content' + optional :include_merged_yaml, type: Boolean, desc: 'If the expanded CI/CD configuration should be included in the response' + optional :include_jobs, type: Boolean, desc: 'If the list of jobs should be included in the response. This is + false by default' end + post '/lint', urgency: :low do unauthorized! unless can_lint_ci? @@ -36,16 +42,21 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Validation of .gitlab-ci.yml content' do - detail 'This feature was introduced in GitLab 13.5.' + desc 'Validates a CI YAML configuration with a namespace' do + detail 'Checks if a project’s latest (HEAD of the project’s default branch) .gitlab-ci.yml configuration is + valid' + success Entities::Ci::Lint::Result + tags %w[ci_lint] end params do - optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.' - optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response' + optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check. This is false by default' + optional :include_jobs, type: Boolean, desc: 'If the list of jobs that would exist in a static check or pipeline + simulation should be included in the response. This is false by default' optional :ref, type: String, desc: 'Branch or tag used to execute a dry run. Defaults to the default branch of the project. Only used when dry_run is true' end + get ':id/ci/lint', urgency: :low do - authorize! :download_code, user_project + authorize! :read_code, user_project if user_project.commit.present? content = user_project.repository.gitlab_ci_yml_for(user_project.commit.id, user_project.ci_config_path_or_default) @@ -60,15 +71,19 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Validation of .gitlab-ci.yml content' do - detail 'This feature was introduced in GitLab 13.6.' + desc 'Validate a CI YAML configuration with a namespace' do + detail 'Checks if CI/CD YAML configuration is valid. This endpoint has namespace specific context' + success code: 200, model: Entities::Ci::Lint::Result + tags %w[ci_lint] end params do requires :content, type: String, desc: 'Content of .gitlab-ci.yml' - optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.' - optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response' - optional :ref, type: String, desc: 'Branch or tag used to execute a dry run. Defaults to the default branch of the project. Only used when dry_run is true' + optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check. This is false by default' + optional :include_jobs, type: Boolean, desc: 'If the list of jobs that would exist in a static check or pipeline + simulation should be included in the response. This is false by default' + optional :ref, type: String, desc: 'When dry_run is true, sets the branch or tag to use. Defaults to the project’s default branch when not set' end + post ':id/ci/lint', urgency: :low do authorize! :create_pipeline, user_project diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb index 1f8255fd6a4..276560f3433 100644 --- a/lib/api/markdown.rb +++ b/lib/api/markdown.rb @@ -7,13 +7,19 @@ module API feature_category :team_planning params do - requires :text, type: String, desc: "The markdown text to render" - optional :gfm, type: Boolean, desc: "Render text using GitLab Flavored Markdown" - optional :project, type: String, desc: "The full path of a project to use as the context when creating references using GitLab Flavored Markdown" + requires :text, type: String, desc: "The Markdown text to render" + optional :gfm, type: Boolean, desc: "Render text using GitLab Flavored Markdown. Default is false" + optional :project, type: String, desc: "Use project as a context when creating references using GitLab Flavored Markdown" end resource :markdown do - desc "Render markdown text" do + desc "Render an arbitrary Markdown document" do detail "This feature was introduced in GitLab 11.0." + success ::API::Entities::Markdown + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' } + ] + tags %w[markdown] end post do context = { only_path: false, current_user: current_user } @@ -29,7 +35,7 @@ module API context[:skip_project_check] = true end - { html: Banzai.render_and_post_process(params[:text], context) } + present({ html: Banzai.render_and_post_process(params[:text], context) }, with: Entities::Markdown) end end end diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb index 72313d6a588..30cdaba76ba 100644 --- a/lib/api/maven_packages.rb +++ b/lib/api/maven_packages.rb @@ -220,7 +220,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Download the maven package file' do @@ -232,18 +232,20 @@ module API end route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true get ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do + project = user_project(action: :read_package) + # return a similar failure to user_project - unless Feature.enabled?(:maven_central_request_forwarding, user_project&.root_ancestor) + unless Feature.enabled?(:maven_central_request_forwarding, project&.root_ancestor) not_found!('Project') unless path_exists?(params[:path]) end - authorize_read_package!(user_project) + authorize_read_package!(project) file_name, format = extract_format(params[:file_name]) - package = fetch_package(file_name: file_name, project: user_project) + package = fetch_package(file_name: file_name, project: project) - find_and_present_package_file(package, file_name, format, params.merge(target: user_project)) + find_and_present_package_file(package, file_name, format, params.merge(target: project)) end desc 'Workhorse authorize the maven package file upload' do @@ -268,7 +270,7 @@ module API params do requires :path, type: String, desc: 'Package path' requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true put ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do diff --git a/lib/api/merge_request_approvals.rb b/lib/api/merge_request_approvals.rb index 71ca8331ed6..7622ec717cc 100644 --- a/lib/api/merge_request_approvals.rb +++ b/lib/api/merge_request_approvals.rb @@ -6,10 +6,9 @@ module API feature_category :source_code_management - helpers do - params :ee_approval_params do - end + helpers ::API::Helpers::MergeRequestsHelpers + helpers do def present_approval(merge_request) present merge_request, with: ::API::Entities::MergeRequestApprovals, current_user: current_user end @@ -24,7 +23,12 @@ module API # merge_request_iid (required) - IID of MR # Examples: # GET /projects/:id/merge_requests/:merge_request_iid/approvals - desc 'List approvals for merge request' + desc 'List approvals for merge request' do + success ::API::Entities::MergeRequestApprovals + failure [ + { code: 404, message: 'Not found' } + ] + end get 'approvals', urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) @@ -39,7 +43,13 @@ module API # Examples: # POST /projects/:id/merge_requests/:merge_request_iid/approve # - desc 'Approve a merge request' + desc 'Approve a merge request' do + success code: 201, model: ::API::Entities::MergeRequestApprovals + failure [ + { code: 404, message: 'Not found' }, + { code: 401, message: 'Unauthorized' } + ] + end params do optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' @@ -60,7 +70,13 @@ module API present_approval(merge_request) end - desc 'Remove an approval from a merge request' + desc 'Remove an approval from a merge request' do + success code: 201, model: ::API::Entities::MergeRequestApprovals + failure [ + { code: 404, message: 'Not found' }, + { code: 401, message: 'Unauthorized' } + ] + end post 'unapprove', urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request) diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb index 87623568a04..c7f0f88eacc 100644 --- a/lib/api/merge_request_diffs.rb +++ b/lib/api/merge_request_diffs.rb @@ -10,16 +10,18 @@ module API feature_category :code_review params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a list of merge request diff versions' do detail 'This feature was introduced in GitLab 8.12.' success Entities::MergeRequestDiff + tags %w[merge_requests] + is_array true end params do - requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request' + requires :merge_request_iid, type: Integer, desc: 'The internal ID of the merge request' use :pagination end get ":id/merge_requests/:merge_request_iid/versions" do @@ -31,11 +33,12 @@ module API desc 'Get a single merge request diff version' do detail 'This feature was introduced in GitLab 8.12.' success Entities::MergeRequestDiffFull + tags %w[merge_requests] end params do - requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request' - requires :version_id, type: Integer, desc: 'The ID of a merge request diff version' + requires :merge_request_iid, type: Integer, desc: 'The internal ID of the merge request' + requires :version_id, type: Integer, desc: 'The ID of the merge request diff version' end get ":id/merge_requests/:merge_request_iid/versions/:version_id", urgency: :low do diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index a0e7d0b10cd..bb2861aa221 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -170,7 +170,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do include TimeTrackingEndpoints diff --git a/lib/api/metadata.rb b/lib/api/metadata.rb index 3e42ffe336a..788d9843c63 100644 --- a/lib/api/metadata.rb +++ b/lib/api/metadata.rb @@ -9,6 +9,8 @@ module API before { authenticate! } + METADATA_TAGS = %w[metadata].freeze + feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned METADATA_QUERY = <<~EOF @@ -21,6 +23,7 @@ module API externalUrl version } + enterprise } } EOF @@ -35,30 +38,13 @@ module API end end - desc 'Retrieve metadata information for this GitLab instance.' do + desc 'Retrieve metadata information for this GitLab instance' do detail 'This feature was introduced in GitLab 15.2.' - success [ - { - code: 200, - model: Entities::Metadata, - message: 'successful operation', - examples: { - successful_response: { - 'value' => { - version: "15.0-pre", - revision: "c401a659d0c", - kas: { - enabled: true, - externalUrl: "grpc://gitlab.example.com:8150", - version: "15.0.0" - } - } - } - } - } + success Entities::Metadata + failure [ + { code: 401, message: 'Unauthorized' } ] - failure [{ code: 401, message: 'unauthorized operation' }] - tags %w[metadata] + tags METADATA_TAGS end get '/metadata' do run_metadata_query @@ -66,31 +52,14 @@ module API # Support the deprecated `/version` route. # See https://gitlab.com/gitlab-org/gitlab/-/issues/366287 - desc 'Get the version information of the GitLab instance.' do + desc 'Retrieves version information for the GitLab instance' do detail 'This feature was introduced in GitLab 8.13 and deprecated in 15.5. ' \ 'We recommend you instead use the Metadata API.' - success [ - { - code: 200, - model: Entities::Metadata, - message: 'successful operation', - examples: { - 'Example' => { - 'value' => { - version: "15.0-pre", - revision: "c401a659d0c", - kas: { - enabled: true, - externalUrl: "grpc://gitlab.example.com:8150", - version: "15.0.0" - } - } - } - } - } + success Entities::Metadata + failure [ + { code: 401, message: 'Unauthorized' } ] - failure [{ code: 401, message: 'unauthorized operation' }] - tags %w[metadata] + tags METADATA_TAGS end get '/version' do diff --git a/lib/api/metrics/dashboard/annotations.rb b/lib/api/metrics/dashboard/annotations.rb index 478adcdce70..6ba154191be 100644 --- a/lib/api/metrics/dashboard/annotations.rb +++ b/lib/api/metrics/dashboard/annotations.rb @@ -7,8 +7,15 @@ module API feature_category :metrics urgency :low - desc 'Create a new monitoring dashboard annotation' do + desc 'Create a new annotation' do + detail 'Creates a new monitoring dashboard annotation' success Entities::Metrics::Dashboard::Annotation + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[dashboard_annotations] end ANNOTATIONS_SOURCES = [ @@ -20,12 +27,16 @@ module API resource annotations_source[:resource] do params do requires :starting_at, type: DateTime, - desc: 'Date time indicating starting moment to which the annotation relates.' + desc: 'Date time string, ISO 8601 formatted, such as 2016-03-11T03:45:40Z.'\ + 'Timestamp marking start point of annotation.' optional :ending_at, type: DateTime, - desc: 'Date time indicating ending moment to which the annotation relates.' + desc: 'Date time string, ISO 8601 formatted, such as 2016-03-11T03:45:40Z.'\ + 'Timestamp marking end point of annotation.'\ + 'When not supplied, an annotation displays as a single event at the start point.' requires :dashboard_path, type: String, coerce_with: -> (val) { CGI.unescape(val) }, - desc: 'The path to a file defining the dashboard on which the annotation should be added' - requires :description, type: String, desc: 'The description of the annotation' + desc: 'ID of the dashboard which needs to be annotated.'\ + 'Treated as a CGI-escaped path, and automatically un-escaped.' + requires :description, type: String, desc: 'Description of the annotation.' end post ':id/metrics_dashboard/annotations' do @@ -33,7 +44,9 @@ module API forbidden! unless can?(current_user, :create_metrics_dashboard_annotation, annotations_source_object) - create_service_params = declared(params).merge(annotations_source[:create_service_param_key] => annotations_source_object) + create_service_params = declared(params).merge( + annotations_source[:create_service_param_key] => annotations_source_object + ) result = ::Metrics::Dashboard::Annotations::CreateService.new(current_user, create_service_params).execute diff --git a/lib/api/metrics/user_starred_dashboards.rb b/lib/api/metrics/user_starred_dashboards.rb index 4d5396acccb..0a91e914d52 100644 --- a/lib/api/metrics/user_starred_dashboards.rb +++ b/lib/api/metrics/user_starred_dashboards.rb @@ -6,14 +6,22 @@ module API feature_category :metrics urgency :low + USER_STARRED_DASHBOARDS_TAGS = %w[user_starred_dashboards].freeze + resource :projects do - desc 'Marks selected metrics dashboard as starred' do + desc 'Add a star to a dashboard' do + detail 'Marks selected metrics dashboard as starred. Introduced in GitLab 13.0.' success Entities::Metrics::UserStarredDashboard + failure [ + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' } + ] + tags USER_STARRED_DASHBOARDS_TAGS end params do requires :dashboard_path, type: String, allow_blank: false, coerce_with: ->(val) { CGI.unescape(val) }, - desc: 'Url encoded path to a file defining the dashboard to which the star should be added' + desc: 'URL-encoded path to file defining the dashboard which should be marked as favorite' end post ':id/metrics/user_starred_dashboards' do @@ -26,7 +34,15 @@ module API end end - desc 'Remove star from selected metrics dashboard' + desc 'Remove a star from a dashboard' do + detail 'Remove star from selected metrics dashboard. Introduced in GitLab 13.0.' + success code: 200 + failure [ + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' } + ] + tags USER_STARRED_DASHBOARDS_TAGS + end params do optional :dashboard_path, type: String, allow_blank: false, coerce_with: ->(val) { CGI.unescape(val) }, diff --git a/lib/api/ml/mlflow.rb b/lib/api/ml/mlflow.rb index 2ffb04ebcbd..56bfac1530e 100644 --- a/lib/api/ml/mlflow.rb +++ b/lib/api/ml/mlflow.rb @@ -68,10 +68,19 @@ module API def find_candidate!(iid) candidate_repository.by_iid(iid) || resource_not_found! end + + def packages_url + path = api_v4_projects_packages_generic_package_version_path( + id: user_project.id, package_name: '', file_name: '' + ) + path = path.delete_suffix('/package_version') + + "#{request.base_url}#{path}" + end end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'API to interface with MLFlow Client, REST API version 1.28.0' do @@ -130,8 +139,7 @@ module API resource :runs do desc 'Creates a Run.' do success Entities::Ml::Mlflow::Run - detail ['https://www.mlflow.org/docs/1.28.0/rest-api.html#create-run', - 'MLFlow Runs map to GitLab Candidates'] + detail 'MLFlow Runs map to GitLab Candidates. https://www.mlflow.org/docs/1.28.0/rest-api.html#create-run' end params do requires :experiment_id, type: Integer, @@ -143,7 +151,8 @@ module API optional :tags, type: Array, desc: 'This will be ignored' end post 'create', urgency: :low do - present candidate_repository.create!(experiment, params[:start_time]), with: Entities::Ml::Mlflow::Run + present candidate_repository.create!(experiment, params[:start_time]), + with: Entities::Ml::Mlflow::Run, packages_url: packages_url end desc 'Gets an MLFlow Run, which maps to GitLab Candidates' do @@ -155,13 +164,12 @@ module API optional :run_uuid, type: String, desc: 'This parameter is ignored' end get 'get', urgency: :low do - present candidate, with: Entities::Ml::Mlflow::Run + present candidate, with: Entities::Ml::Mlflow::Run, packages_url: packages_url end desc 'Updates a Run.' do success Entities::Ml::Mlflow::UpdateRun - detail ['https://www.mlflow.org/docs/1.28.0/rest-api.html#update-run', - 'MLFlow Runs map to GitLab Candidates'] + detail 'MLFlow Runs map to GitLab Candidates. https://www.mlflow.org/docs/1.28.0/rest-api.html#update-run' end params do requires :run_id, type: String, desc: 'UUID of the candidate.' @@ -174,7 +182,7 @@ module API post 'update', urgency: :low do candidate_repository.update(candidate, params[:status], params[:end_time]) - present candidate, with: Entities::Ml::Mlflow::UpdateRun + present candidate, with: Entities::Ml::Mlflow::UpdateRun, packages_url: packages_url end desc 'Logs a metric to a run.' do diff --git a/lib/api/npm_project_packages.rb b/lib/api/npm_project_packages.rb index 166c0b755fe..494b493f5e0 100644 --- a/lib/api/npm_project_packages.rb +++ b/lib/api/npm_project_packages.rb @@ -11,7 +11,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end namespace 'projects/:id/packages/npm' do desc 'Download the NPM tarball' do diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb index 3e05ea13311..d549a8be035 100644 --- a/lib/api/nuget_project_packages.rb +++ b/lib/api/nuget_project_packages.rb @@ -35,7 +35,7 @@ module API helpers do params :file_params do - requires :package, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + requires :package, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end def project_or_group @@ -91,7 +91,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do namespace ':id/packages/nuget' do diff --git a/lib/api/package_files.rb b/lib/api/package_files.rb index 278dc4c2044..bb9f96cdbb1 100644 --- a/lib/api/package_files.rb +++ b/lib/api/package_files.rb @@ -8,19 +8,23 @@ module API authorize_packages_access!(user_project) end + PACKAGE_FILES_TAGS = %w[package_files].freeze + feature_category :package_registry urgency :low helpers ::API::Helpers::PackagesHelpers params do - requires :id, type: String, desc: 'The ID of a project' - requires :package_id, type: Integer, desc: 'The ID of a package' + requires :id, types: [String, Integer], desc: 'ID or URL-encoded path of the project' + requires :package_id, type: Integer, desc: 'ID of a package' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get all package files' do - detail 'This feature was introduced in GitLab 11.8' + desc 'List package files' do + detail 'Get a list of package files of a single package' success ::API::Entities::PackageFile + is_array true + tags PACKAGE_FILES_TAGS end params do use :pagination @@ -35,11 +39,17 @@ module API present paginate(package_files), with: ::API::Entities::PackageFile end - desc 'Remove a package file' do + desc 'Delete a package file' do detail 'This feature was introduced in GitLab 13.12' + success code: 204 + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags PACKAGE_FILES_TAGS end params do - requires :package_file_id, type: Integer, desc: 'The ID of a package file' + requires :package_file_id, type: Integer, desc: 'ID of a package file' end delete ':id/packages/:package_id/package_files/:package_file_id' do authorize_destroy_package!(user_project) diff --git a/lib/api/pages.rb b/lib/api/pages.rb index 5f695f3853d..7e230bd3c67 100644 --- a/lib/api/pages.rb +++ b/lib/api/pages.rb @@ -10,7 +10,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Unpublish pages' do diff --git a/lib/api/pages_domains.rb b/lib/api/pages_domains.rb index 9cf61967ba4..967847a8e62 100644 --- a/lib/api/pages_domains.rb +++ b/lib/api/pages_domains.rb @@ -54,7 +54,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before do diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb index bdb69d0ba44..c3505780396 100644 --- a/lib/api/pagination_params.rb +++ b/lib/api/pagination_params.rb @@ -17,8 +17,9 @@ module API included do helpers do params :pagination do - optional :page, type: Integer, default: 1, desc: 'Current page number' - optional :per_page, type: Integer, default: 20, desc: 'Number of items per page', except_values: [0] + optional :page, type: Integer, default: 1, desc: 'Current page number', documentation: { example: 1 } + optional :per_page, type: Integer, default: 20, + desc: 'Number of items per page', except_values: [0], documentation: { example: 20 } end def verify_pagination_params! diff --git a/lib/api/personal_access_tokens.rb b/lib/api/personal_access_tokens.rb index a2903faa4ad..66930ecd797 100644 --- a/lib/api/personal_access_tokens.rb +++ b/lib/api/personal_access_tokens.rb @@ -6,24 +6,6 @@ module API feature_category :authentication_and_authorization - desc 'Get all Personal Access Tokens' do - detail 'This feature was added in GitLab 13.3' - success Entities::PersonalAccessToken - end - params do - optional :user_id, type: Integer, desc: 'Filter PATs by User ID' - optional :revoked, type: Boolean, desc: 'Filter PATs where revoked state matches parameter' - optional :state, type: String, desc: 'Filter PATs which are either active or not', - values: %w[active inactive] - optional :created_before, type: DateTime, desc: 'Filter PATs which were created before given datetime' - optional :created_after, type: DateTime, desc: 'Filter PATs which were created after given datetime' - optional :last_used_before, type: DateTime, desc: 'Filter PATs which were used before given datetime' - optional :last_used_after, type: DateTime, desc: 'Filter PATs which were used after given datetime' - optional :search, type: String, desc: 'Filters PATs by its name' - - use :pagination - end - before do authenticate! restrict_non_admins! unless current_user.can_admin_all_resources? @@ -32,12 +14,47 @@ module API helpers ::API::Helpers::PersonalAccessTokensHelpers resources :personal_access_tokens do + desc 'List personal access tokens' do + detail 'Get all personal access tokens the authenticated user has access to.' + is_array true + success Entities::PersonalAccessToken + tags %w[personal_access_tokens] + failure [ + { code: 401, message: 'Unauthorized' } + ] + end + params do + optional :user_id, type: Integer, desc: 'Filter PATs by User ID', documentation: { example: 2 } + optional :revoked, type: Boolean, desc: 'Filter PATs where revoked state matches parameter', + documentation: { example: false } + optional :state, type: String, desc: 'Filter PATs which are either active or not', + values: %w[active inactive], documentation: { example: 'active' } + optional :created_before, type: DateTime, desc: 'Filter PATs which were created before given datetime', + documentation: { example: '2022-01-01' } + optional :created_after, type: DateTime, desc: 'Filter PATs which were created after given datetime', + documentation: { example: '2021-01-01' } + optional :last_used_before, type: DateTime, desc: 'Filter PATs which were used before given datetime', + documentation: { example: '2021-01-01' } + optional :last_used_after, type: DateTime, desc: 'Filter PATs which were used after given datetime', + documentation: { example: '2022-01-01' } + optional :search, type: String, desc: 'Filters PATs by its name', documentation: { example: 'token' } + + use :pagination + end get do tokens = PersonalAccessTokensFinder.new(finder_params(current_user), current_user).execute present paginate(tokens), with: Entities::PersonalAccessToken end + desc 'Get single personal access token' do + detail 'Get a personal access token by using the ID of the personal access token.' + success Entities::PersonalAccessToken + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + end get ':id' do token = PersonalAccessToken.find_by_id(params[:id]) @@ -51,6 +68,13 @@ module API end end + desc 'Revoke a personal access token' do + detail 'Revoke a personal access token by using the ID of the personal access token.' + success code: 204 + failure [ + { code: 400, message: 'Bad Request' } + ] + end delete ':id' do token = find_token(params[:id]) diff --git a/lib/api/personal_access_tokens/self_information.rb b/lib/api/personal_access_tokens/self_information.rb index 89850614f94..5735fe49f33 100644 --- a/lib/api/personal_access_tokens/self_information.rb +++ b/lib/api/personal_access_tokens/self_information.rb @@ -17,10 +17,28 @@ module API before { authenticate! } resource :personal_access_tokens do + desc "Get single personal access token" do + detail 'Get the details of a personal access token by passing it to the API in a header' + success code: 200, model: Entities::PersonalAccessToken + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[personal_access_tokens] + end get 'self' do present access_token, with: Entities::PersonalAccessToken end + desc "Revoke a personal access token" do + detail 'Revoke a personal access token by passing it to the API in a header' + success code: 204 + failure [ + { code: 400, message: 'Bad Request' } + ] + tags %w[personal_access_tokens] + end + delete 'self' do revoke_token(access_token) end diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb index 4644d38ea80..21f1ee69613 100644 --- a/lib/api/project_clusters.rb +++ b/lib/api/project_clusters.rb @@ -13,12 +13,17 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of the project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get all clusters from the project' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'List project clusters' do + detail 'This feature was introduced in GitLab 11.7. Returns a list of project clusters.' success Entities::Cluster + failure [ + { code: 403, message: 'Forbidden' } + ] + is_array true + tags %w[clusters] end params do use :pagination @@ -29,9 +34,14 @@ module API present paginate(clusters_for_current_user), with: Entities::Cluster end - desc 'Get specific cluster for the project' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'Get a single project cluster' do + detail 'This feature was introduced in GitLab 11.7. Gets a single project cluster.' success Entities::ClusterProject + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :cluster_id, type: Integer, desc: 'The cluster ID' @@ -42,9 +52,15 @@ module API present cluster, with: Entities::ClusterProject end - desc 'Adds an existing cluster' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'Add existing cluster to project' do + detail 'This feature was introduced in GitLab 11.7. Adds an existing Kubernetes cluster to the project.' success Entities::ClusterProject + failure [ + { code: 400, message: 'Validation error' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :name, type: String, desc: 'Cluster name' @@ -76,9 +92,15 @@ module API end end - desc 'Update an existing cluster' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'Edit project cluster' do + detail 'This feature was introduced in GitLab 11.7. Updates an existing project cluster.' success Entities::ClusterProject + failure [ + { code: 400, message: 'Validation error' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :cluster_id, type: Integer, desc: 'The cluster ID' @@ -108,9 +130,14 @@ module API end end - desc 'Remove a cluster' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'Delete project cluster' do + detail 'This feature was introduced in GitLab 11.7. Deletes an existing project cluster. Does not remove existing resources within the connected Kubernetes cluster.' success Entities::ClusterProject + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[clusters] end params do requires :cluster_id, type: Integer, desc: 'The Cluster ID' diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb index 6a6275ed02a..c5add42decc 100644 --- a/lib/api/project_container_repositories.rb +++ b/lib/api/project_container_repositories.rb @@ -16,7 +16,7 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end route_setting :authentication, job_token_allowed: true, job_token_scope: :project resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do @@ -47,8 +47,12 @@ module API end delete ':id/registry/repositories/:repository_id', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do authorize_admin_container_image! + repository.delete_scheduled! + + unless Feature.enabled?(:container_registry_delete_repository_with_cron_worker) + DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id) # rubocop:disable CodeReuse/Worker + end - DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id) # rubocop:disable CodeReuse/Worker track_package_event('delete_repository', :container, user: current_user, project: user_project, namespace: user_project.namespace) status :accepted diff --git a/lib/api/project_debian_distributions.rb b/lib/api/project_debian_distributions.rb index b8ca9428fa3..1e27f5c8856 100644 --- a/lib/api/project_debian_distributions.rb +++ b/lib/api/project_debian_distributions.rb @@ -3,7 +3,7 @@ module API class ProjectDebianDistributions < ::API::Base params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end before do diff --git a/lib/api/project_events.rb b/lib/api/project_events.rb index e8829216336..d90ce32c354 100644 --- a/lib/api/project_events.rb +++ b/lib/api/project_events.rb @@ -12,10 +12,15 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' + optional :action, type: String, desc: 'Include only events of a particular action type' + optional :target_type, type: String, desc: 'Include only events of a particular target type' + optional :before, type: DateTime, desc: 'Include only events created before a particular date' + optional :after, type: DateTime, desc: 'Include only events created after a particular date' + optional :sort, type: String, desc: 'Sort events in asc or desc order by created_at. Default is desc' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc "List a Project's visible events" do + desc "List a project's visible events" do success Entities::Event end params do diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index 29fdfe45566..e4e950fb603 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -11,12 +11,19 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: { id: %r{[^/]+} } do desc 'Get export status' do detail 'This feature was introduced in GitLab 10.6.' - success Entities::ProjectExportStatus + success code: 200, model: Entities::ProjectExportStatus + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] end get ':id/export' do present user_project, with: Entities::ProjectExportStatus @@ -24,6 +31,15 @@ module API desc 'Download export' do detail 'This feature was introduced in GitLab 10.6.' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] + produces %w[application/octet-stream application/json] end get ':id/export/download' do check_rate_limit! :project_download_export, scope: [current_user, user_project.namespace] @@ -41,6 +57,16 @@ module API desc 'Start export' do detail 'This feature was introduced in GitLab 10.6.' + success code: 202 + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 429, message: 'Too many requests' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] end params do optional :description, type: String, desc: 'Override the project description' @@ -86,6 +112,15 @@ module API desc 'Start relations export' do detail 'This feature was introduced in GitLab 14.4' + success code: 202 + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] end post ':id/export_relations' do response = ::BulkImports::ExportService.new(portable: user_project, user: current_user).execute @@ -93,12 +128,23 @@ module API if response.success? accepted! else - render_api_error!(message: 'Project relations export could not be started.') + render_api_error!('Project relations export could not be started.', 500) end end desc 'Download relations export' do detail 'This feature was introduced in GitLab 14.4' + success code: 200 + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 500, message: 'Internal Server Error' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] + produces %w[application/octet-stream application/json] end params do requires :relation, @@ -119,6 +165,15 @@ module API desc 'Relations export status' do detail 'This feature was introduced in GitLab 14.4' + is_array true + success code: 200, model: Entities::BulkImports::ExportStatus + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] end get ':id/export_relations/status' do present user_project.bulk_import_exports, with: Entities::BulkImports::ExportStatus diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 466e80d68c8..ced8ecec883 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -4,6 +4,8 @@ module API class ProjectHooks < ::API::Base include PaginationParams + project_hooks_tags = %w[project_hooks] + before { authenticate! } before { authorize_admin_project } @@ -37,15 +39,18 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do namespace ':id/hooks' do mount ::API::Hooks::UrlVariables end - desc 'Get project hooks' do + desc 'List project hooks' do + detail 'Get a list of project hooks' success Entities::ProjectHook + is_array true + tags project_hooks_tags end params do use :pagination @@ -54,8 +59,13 @@ module API present paginate(user_project.hooks), with: Entities::ProjectHook end - desc 'Get a project hook' do + desc 'Get project hook' do + detail 'Get a specific hook for a project' success Entities::ProjectHook + failure [ + { code: 404, message: 'Not found' } + ] + tags project_hooks_tags end params do requires :hook_id, type: Integer, desc: 'The ID of a project hook' @@ -65,8 +75,15 @@ module API present hook, with: Entities::ProjectHook end - desc 'Add hook to project' do + desc 'Add project hook' do + detail 'Adds a hook to a specified project' success Entities::ProjectHook + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags project_hooks_tags end params do use :requires_url @@ -79,11 +96,18 @@ module API save_hook(hook, Entities::ProjectHook) end - desc 'Update an existing hook' do + desc 'Edit project hook' do + detail 'Edits a hook for a specified project.' success Entities::ProjectHook + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags project_hooks_tags end params do - requires :hook_id, type: Integer, desc: "The ID of the hook to update" + requires :hook_id, type: Integer, desc: 'The ID of the project hook' use :optional_url use :common_hook_parameters end @@ -91,11 +115,16 @@ module API update_hook(entity: Entities::ProjectHook) end - desc 'Deletes project hook' do + desc 'Delete a project hook' do + detail 'Removes a hook from a project. This is an idempotent method and can be called multiple times. Either the hook is available or not.' success Entities::ProjectHook + failure [ + { code: 404, message: 'Not found' } + ] + tags project_hooks_tags end params do - requires :hook_id, type: Integer, desc: 'The ID of the hook to delete' + requires :hook_id, type: Integer, desc: 'The ID of the project hook' end delete ":id/hooks/:hook_id" do hook = find_hook diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index 0da8c1ecedd..02f0d9a2a70 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -40,6 +40,7 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Workhorse authorize the project import upload' do detail 'This feature was introduced in GitLab 12.9' + tags ['project_import'] end post 'import/authorize' do require_gitlab_workhorse! @@ -77,7 +78,16 @@ module API end desc 'Create a new project import' do detail 'This feature was introduced in GitLab 10.6.' - success Entities::ProjectImportStatus + success code: 201, model: Entities::ProjectImportStatus + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_import'] + consumes ['multipart/form-data'] end post 'import' do require_gitlab_workhorse! @@ -108,11 +118,19 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end desc 'Get a project import status' do detail 'This feature was introduced in GitLab 10.6.' - success Entities::ProjectImportStatus + success code: 200, model: Entities::ProjectImportStatus + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_import'] end route_setting :skip_authentication, true get ':id/import' do @@ -133,7 +151,17 @@ module API end desc 'Create a new project import using a remote object storage path' do detail 'This feature was introduced in GitLab 13.2.' - success Entities::ProjectImportStatus + consumes ['multipart/form-data'] + tags ['project_import'] + success code: 201, model: Entities::ProjectImportStatus + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' }, + { code: 429, message: 'Too many requests' }, + { code: 503, message: 'Service unavailable' } + ] end post 'remote-import' do check_rate_limit! :project_import, scope: [current_user, :project_import] @@ -176,7 +204,17 @@ module API end desc 'Create a new project import using a file from AWS S3' do detail 'This feature was introduced in GitLab 14.9.' - success Entities::ProjectImportStatus + consumes ['multipart/form-data'] + tags ['project_import'] + success code: 201, model: Entities::ProjectImportStatus + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' }, + { code: 429, message: 'Too many requests' }, + { code: 503, message: 'Service unavailable' } + ] end post 'remote-import-s3' do not_found! unless ::Feature.enabled?(:import_project_from_remote_file_s3) diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb index 9f82dbf9813..a7a583aaa23 100644 --- a/lib/api/project_milestones.rb +++ b/lib/api/project_milestones.rb @@ -11,7 +11,7 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a list of project milestones' do diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb index 800966408fc..d09c481403f 100644 --- a/lib/api/project_packages.rb +++ b/lib/api/project_packages.rb @@ -14,7 +14,7 @@ module API helpers ::API::Helpers::PackagesHelpers params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get all project packages' do diff --git a/lib/api/project_repository_storage_moves.rb b/lib/api/project_repository_storage_moves.rb index ab5d8b3a888..5777b8754e7 100644 --- a/lib/api/project_repository_storage_moves.rb +++ b/lib/api/project_repository_storage_moves.rb @@ -11,7 +11,8 @@ module API resource :project_repository_storage_moves do desc 'Get a list of all project repository storage moves' do detail 'This feature was introduced in GitLab 13.0.' - success Entities::Projects::RepositoryStorageMove + is_array true + success code: 200, model: Entities::Projects::RepositoryStorageMove end params do use :pagination @@ -24,7 +25,7 @@ module API desc 'Get a project repository storage move' do detail 'This feature was introduced in GitLab 13.0.' - success Entities::Projects::RepositoryStorageMove + success code: 200, model: Entities::Projects::RepositoryStorageMove end params do requires :repository_storage_move_id, type: Integer, desc: 'The ID of a project repository storage move' @@ -37,6 +38,7 @@ module API desc 'Schedule bulk project repository storage moves' do detail 'This feature was introduced in GitLab 13.7.' + success code: 202 end params do requires :source_storage_name, type: String, desc: 'The source storage shard', values: -> { Gitlab.config.repositories.storages.keys } @@ -53,12 +55,13 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a list of all project repository storage moves' do detail 'This feature was introduced in GitLab 13.1.' - success Entities::Projects::RepositoryStorageMove + is_array true + success code: 200, model: Entities::Projects::RepositoryStorageMove end params do use :pagination @@ -71,7 +74,7 @@ module API desc 'Get a project repository storage move' do detail 'This feature was introduced in GitLab 13.1.' - success Entities::Projects::RepositoryStorageMove + success code: 200, model: Entities::Projects::RepositoryStorageMove end params do requires :repository_storage_move_id, type: Integer, desc: 'The ID of a project repository storage move' @@ -84,14 +87,14 @@ module API desc 'Schedule a project repository storage move' do detail 'This feature was introduced in GitLab 13.1.' - success Entities::Projects::RepositoryStorageMove + success code: 201, model: Entities::Projects::RepositoryStorageMove end params do optional :destination_storage_name, type: String, desc: 'The destination storage shard' end post ':id/repository_storage_moves' do storage_move = user_project.repository_storage_moves.build( - declared_params.merge(source_storage_name: user_project.repository_storage) + declared_params.compact.merge(source_storage_name: user_project.repository_storage) ) if storage_move.schedule diff --git a/lib/api/project_snapshots.rb b/lib/api/project_snapshots.rb index d33d2976b1c..d2ed7f75fb7 100644 --- a/lib/api/project_snapshots.rb +++ b/lib/api/project_snapshots.rb @@ -11,6 +11,11 @@ module API resource :projects do desc 'Download a (possibly inconsistent) snapshot of a repository' do detail 'This feature was introduced in GitLab 10.7' + success File + produces 'application/x-tar' + failure [ + { code: 401, message: 'Unauthorized' } + ] end params do optional :wiki, type: Boolean, desc: 'Set to true to receive the wiki repository' diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 14792730eae..93ffb23fea8 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -9,7 +9,7 @@ module API feature_category :snippets params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do helpers Helpers::SnippetsHelpers @@ -34,6 +34,11 @@ module API desc 'Get all project snippets' do success Entities::ProjectSnippet + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[project_snippets] + is_array true end params do use :pagination @@ -46,6 +51,10 @@ module API desc 'Get a single project snippet' do success Entities::ProjectSnippet + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[project_snippets] end params do requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' @@ -60,6 +69,12 @@ module API desc 'Create a new project snippet' do success Entities::ProjectSnippet + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[project_snippets] end params do requires :title, type: String, allow_blank: false, desc: 'The title of the snippet' @@ -91,6 +106,12 @@ module API desc 'Update an existing project snippet' do success Entities::ProjectSnippet + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[project_snippets] end params do requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' @@ -132,7 +153,14 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - desc 'Delete a project snippet' + desc 'Delete a project snippet' do + success code: 204 + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' } + ] + tags %w[project_snippets] + end params do requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' end @@ -156,7 +184,13 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - desc 'Get a raw project snippet' + desc 'Get a raw project snippet' do + success Entities::ProjectSnippet + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[project_snippets] + end params do requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' end @@ -168,7 +202,13 @@ module API present content_for(snippet) end - desc 'Get raw project snippet file contents from the repository' + desc 'Get raw project snippet file contents from the repository' do + success Entities::ProjectSnippet + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[project_snippets] + end params do use :raw_file_params end @@ -182,6 +222,10 @@ module API desc 'Get the user agent details for a project snippet' do success Entities::UserAgentDetail + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[project_snippets] end params do requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' diff --git a/lib/api/project_statistics.rb b/lib/api/project_statistics.rb index 3db8d20ebac..859e53b6981 100644 --- a/lib/api/project_statistics.rb +++ b/lib/api/project_statistics.rb @@ -10,10 +10,18 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get the list of project fetch statistics for the last 30 days' + desc 'Get the list of project fetch statistics for the last 30 days' do + success Entities::ProjectDailyStatistics + failure [ + { code: 404, message: '404 Project Not Found' }, + { code: 401, message: '401 Unauthorized' } + ] + tags %w[projects] + end + get ":id/statistics" do statistic_finder = ::Projects::DailyStatisticsFinder.new(user_project) diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb index f6e1286d616..8ec67988e39 100644 --- a/lib/api/project_templates.rb +++ b/lib/api/project_templates.rb @@ -15,12 +15,18 @@ module API feature_category :source_code_management params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' requires :type, type: String, values: TEMPLATE_TYPES, desc: 'The type (dockerfiles|gitignores|gitlab_ci_ymls|licenses|metrics_dashboard_ymls|issues|merge_requests) of the template' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a list of templates available to this project' do detail 'This endpoint was introduced in GitLab 11.4' + is_array true + success Entities::TemplatesList + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] end params do use :pagination @@ -33,13 +39,24 @@ module API desc 'Download a template available to this project' do detail 'This endpoint was introduced in GitLab 11.4' + success Entities::License + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] end params do - requires :name, type: String, desc: 'The name of the template' + requires :name, type: String, + desc: 'The key of the template, as obtained from the collection endpoint.', documentation: { example: 'MIT' } optional :source_template_project_id, type: Integer, - desc: 'The project id where a given template is being stored. This is useful when multiple templates from different projects have the same name' - optional :project, type: String, desc: 'The project name to use when expanding placeholders in the template. Only affects licenses' - optional :fullname, type: String, desc: 'The full name of the copyright holder to use when expanding placeholders in the template. Only affects licenses' + desc: 'The project id where a given template is being stored. This is useful when multiple templates from different projects have the same name', + documentation: { example: 1 } + optional :project, type: String, + desc: 'The project name to use when expanding placeholders in the template. Only affects licenses', + documentation: { example: 'GitLab' } + optional :fullname, type: String, + desc: 'The full name of the copyright holder to use when expanding placeholders in the template. Only affects licenses', + documentation: { example: 'GitLab B.V.' } end get ':id/templates/:type/:name', requirements: TEMPLATE_NAMES_ENDPOINT_REQUIREMENTS do diff --git a/lib/api/projects.rb b/lib/api/projects.rb index bb97f4fa7ce..fc898c30a71 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -151,7 +151,6 @@ module API project_params = project_finder_params support_order_by_similarity!(project_params) verify_project_filters!(project_params) - ProjectsFinder.new(current_user: current_user, params: project_params).execute end @@ -336,7 +335,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a single project' do @@ -424,7 +423,7 @@ module API end desc 'Check pages access of this project' - get ':id/pages_access', feature_category: :pages do + get ':id/pages_access', urgency: :low, feature_category: :pages do authorize! :read_pages_content, user_project unless user_project.public_pages? status 200 end @@ -654,7 +653,7 @@ module API desc 'Upload a file' params do - requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded' + requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded', documentation: { type: 'file' } end post ":id/uploads", feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned log_if_upload_exceed_max_size(user_project, params[:file]) diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb index fb782b49f02..bb1420534f1 100644 --- a/lib/api/projects_relation_builder.rb +++ b/lib/api/projects_relation_builder.rb @@ -10,9 +10,13 @@ module API execute_batch_counting(projects_relation) + postload_relation(projects_relation, options) + preload_repository_cache(projects_relation) Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_relation, options[:current_user]).execute if options[:current_user] + + options[:current_user].preloaded_member_roles_for_projects(projects_relation) if options[:current_user] Preloaders::SingleHierarchyProjectGroupPlansPreloader.new(projects_relation).execute if options[:single_hierarchy] preload_groups(projects_relation) if options[:with] == Entities::Project diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb index 38bafac25b2..786045684b8 100644 --- a/lib/api/protected_branches.rb +++ b/lib/api/protected_branches.rb @@ -13,15 +13,23 @@ module API helpers Helpers::ProtectedBranchesHelpers params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, + types: [String, Integer], + desc: 'The ID or URL-encoded path of the project', + documentation: { example: 'gitlab-org/gitlab' } end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc "Get a project's protected branches" do - success Entities::ProtectedBranch + success code: 200, model: Entities::ProtectedBranch + is_array true + failure [ + { code: 404, message: '404 Project Not Found' }, + { code: 401, message: '401 Unauthorized' } + ] end params do use :pagination - optional :search, type: String, desc: 'Search for a protected branch by name' + optional :search, type: String, desc: 'Search for a protected branch by name', documentation: { example: 'mai' } end # rubocop: disable CodeReuse/ActiveRecord get ':id/protected_branches' do @@ -36,10 +44,14 @@ module API # rubocop: enable CodeReuse/ActiveRecord desc 'Get a single protected branch' do - success Entities::ProtectedBranch + success code: 200, model: Entities::ProtectedBranch + failure [ + { code: 404, message: '404 Project Not Found' }, + { code: 401, message: '401 Unauthorized' } + ] end params do - requires :name, type: String, desc: 'The name of the branch or wildcard' + requires :name, type: String, desc: 'The name of the branch or wildcard', documentation: { example: 'main' } end # rubocop: disable CodeReuse/ActiveRecord get ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do @@ -50,10 +62,16 @@ module API # rubocop: enable CodeReuse/ActiveRecord desc 'Protect a single branch' do - success Entities::ProtectedBranch + success code: 201, model: Entities::ProtectedBranch + failure [ + { code: 422, message: 'name is missing' }, + { code: 409, message: "Protected branch 'main' already exists" }, + { code: 404, message: '404 Project Not Found' }, + { code: 401, message: '401 Unauthorized' } + ] end params do - requires :name, type: String, desc: 'The name of the protected branch' + requires :name, type: String, desc: 'The name of the protected branch', documentation: { example: 'main' } optional :push_access_level, type: Integer, values: ProtectedBranch::PushAccessLevel.allowed_access_levels, desc: 'Access levels allowed to push (defaults: `40`, maintainer access level)' @@ -86,9 +104,47 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + desc 'Update a protected branch' do + success code: 200, model: Entities::ProtectedBranch + failure [ + { code: 422, message: 'Push access levels access level has already been taken' }, + { code: 404, message: '404 Project Not Found' }, + { code: 401, message: '401 Unauthorized' } + ] + end + params do + requires :name, type: String, desc: 'The name of the branch', documentation: { example: 'main' } + optional :allow_force_push, type: Boolean, + desc: 'Allow force push for all users with push access.' + + use :optional_params_ee + end + # rubocop: disable CodeReuse/ActiveRecord + patch ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do + protected_branch = user_project.protected_branches.find_by!(name: params[:name]) + + declared_params = declared_params(include_missing: false) + api_service = ::ProtectedBranches::ApiService.new(user_project, current_user, declared_params) + protected_branch = api_service.update(protected_branch) + + if protected_branch.valid? + present protected_branch, with: Entities::ProtectedBranch, project: user_project + else + render_api_error!(protected_branch.errors.full_messages, 422) + end + end + # rubocop: enable CodeReuse/ActiveRecord + desc 'Unprotect a single branch' params do - requires :name, type: String, desc: 'The name of the protected branch' + requires :name, type: String, desc: 'The name of the protected branch', documentation: { example: 'main' } + end + desc 'Unprotect a single branch' do + success code: 204 + failure [ + { code: 404, message: '404 Project Not Found' }, + { code: 401, message: '401 Unauthorized' } + ] end # rubocop: disable CodeReuse/ActiveRecord delete ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS, urgency: :low do diff --git a/lib/api/protected_tags.rb b/lib/api/protected_tags.rb index 4611ee58479..7b55b1fd61d 100644 --- a/lib/api/protected_tags.rb +++ b/lib/api/protected_tags.rb @@ -13,12 +13,18 @@ module API helpers Helpers::ProtectedTagsHelpers params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc "Get a project's protected tags" do detail 'This feature was introduced in GitLab 11.3.' - success Entities::ProtectedTag + is_array true + success code: 200, model: Entities::ProtectedTag + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[protected_tags] end params do use :pagination @@ -33,10 +39,15 @@ module API desc 'Get a single protected tag' do detail 'This feature was introduced in GitLab 11.3.' - success Entities::ProtectedTag + success code: 200, model: Entities::ProtectedTag + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[protected_tags] end params do - requires :name, type: String, desc: 'The name of the tag or wildcard' + requires :name, type: String, desc: 'The name of the tag or wildcard', documentation: { example: 'release*' } end # rubocop: disable CodeReuse/ActiveRecord get ':id/protected_tags/:name', requirements: TAG_ENDPOINT_REQUIREMENTS do @@ -48,13 +59,21 @@ module API desc 'Protect a single tag or wildcard' do detail 'This feature was introduced in GitLab 11.3.' - success Entities::ProtectedTag + success code: 201, model: Entities::ProtectedTag + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[protected_tags] end params do - requires :name, type: String, desc: 'The name of the protected tag' - optional :create_access_level, type: Integer, - values: ProtectedTag::CreateAccessLevel.allowed_access_levels, - desc: 'Access levels allowed to create (defaults: `40`, maintainer access level)' + requires :name, type: String, desc: 'The name of the protected tag', documentation: { example: 'release-1-0' } + optional :create_access_level, + type: Integer, + values: ProtectedTag::CreateAccessLevel.allowed_access_levels, + desc: 'Access levels allowed to create (defaults: `40`, maintainer access level)', + documentation: { example: 30 } use :optional_params_ee end post ':id/protected_tags' do @@ -76,9 +95,16 @@ module API desc 'Unprotect a single tag' do detail 'This feature was introduced in GitLab 11.3.' + success code: 204 + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' }, + { code: 412, message: 'Precondition Failed' } + ] + tags %w[protected_tags] end params do - requires :name, type: String, desc: 'The name of the protected tag' + requires :name, type: String, desc: 'The name of the protected tag', documentation: { example: 'release-1-0' } end # rubocop: disable CodeReuse/ActiveRecord delete ':id/protected_tags/:name', requirements: TAG_ENDPOINT_REQUIREMENTS do diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb index 1f27fcce879..6c649483da1 100644 --- a/lib/api/pypi_packages.rb +++ b/lib/api/pypi_packages.rb @@ -95,9 +95,9 @@ module API find_authorized_group! end - def ensure_project! + def project!(action: :read_package) find_project(params[:id]) || not_found! - authorized_user_project + authorized_user_project(action: action) end end @@ -157,14 +157,10 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - before do - ensure_project! - end - namespace ':id/packages/pypi' do desc 'The PyPi package download endpoint' do detail 'This feature was introduced in GitLab 12.10' @@ -176,8 +172,7 @@ module API route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get 'files/:sha256/*file_identifier' do - project = authorized_user_project - authorize_read_package!(project) + project = project! filename = "#{params[:file_identifier]}.#{params[:format]}" package = Packages::Pypi::PackageFinder.new(current_user, project, { filename: filename, sha256: params[:sha256] }).execute @@ -196,7 +191,7 @@ module API # PyPi simple API returns a list of packages as a simple HTML file. route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get 'simple', format: :txt do - present_simple_index(authorized_user_project) + present_simple_index(project!) end desc 'The PyPi Simple Project Package Endpoint' do @@ -211,7 +206,7 @@ module API # PyPi simple API returns the package descriptor as a simple HTML file. route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get 'simple/*package_name', format: :txt do - present_simple_package(authorized_user_project) + present_simple_package(project!) end desc 'The PyPi Package upload endpoint' do @@ -219,7 +214,7 @@ module API end params do - requires :content, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + requires :content, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } requires :name, type: String requires :version, type: String optional :requires_python, type: String @@ -229,15 +224,16 @@ module API route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth post do - authorize_upload!(authorized_user_project) - bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:pypi_max_file_size, params[:content].size) + project = project!(action: :read_project) + authorize_upload!(project) + bad_request!('File is too large') if project.actual_limits.exceeded?(:pypi_max_file_size, params[:content].size) - track_package_event('push_package', :pypi, project: authorized_user_project, user: current_user, namespace: authorized_user_project.namespace) + track_package_event('push_package', :pypi, project: project, user: current_user, namespace: project.namespace) unprocessable_entity! if Gitlab::FIPS.enabled? && declared_params[:md5_digest].present? ::Packages::Pypi::CreatePackageService - .new(authorized_user_project, current_user, declared_params.merge(build: current_authenticated_job)) + .new(project, current_user, declared_params.merge(build: current_authenticated_job)) .execute created! @@ -249,10 +245,11 @@ module API route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth post 'authorize' do + project = project!(action: :read_project) authorize_workhorse!( - subject: authorized_user_project, + subject: project, has_length: false, - maximum_size: authorized_user_project.actual_limits.pypi_max_file_size + maximum_size: project.actual_limits.pypi_max_file_size ) end end diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb index 8b9380b332e..c72f90dfdf3 100644 --- a/lib/api/release/links.rb +++ b/lib/api/release/links.rb @@ -5,6 +5,8 @@ module API class Links < ::API::Base include PaginationParams + release_links_tags = %w[release_links] + RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS .merge(tag_name: API::NO_SLASH_URL_PART_REGEX) @@ -14,17 +16,23 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, type: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do params do - requires :tag_name, type: String, desc: 'The name of the tag', as: :tag + requires :tag_name, type: String, desc: 'The tag associated with the release', as: :tag end resource 'releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do resource :assets do - desc 'Get a list of links of a release' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'List links of a release' do + detail 'Get assets as links from a release. This feature was introduced in GitLab 11.7.' success Entities::Releases::Link + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags release_links_tags end params do use :pagination @@ -36,15 +44,24 @@ module API present paginate(release.links.sorted), with: Entities::Releases::Link end - desc 'Create a link of a release' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'Create a release link' do + detail 'Create an asset as a link from a release. This feature was introduced in GitLab 11.7.' success Entities::Releases::Link + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' } + ] + tags release_links_tags end params do - requires :name, type: String, desc: 'The name of the link' - requires :url, type: String, desc: 'The URL of the link' - optional :filepath, type: String, desc: 'The filepath of the link' - optional :link_type, type: String, desc: 'The link type, one of: "runbook", "image", "package" or "other"' + requires :name, type: String, desc: 'The name of the link. Link names must be unique in the release' + requires :url, type: String, desc: 'The URL of the link. Link URLs must be unique in the release.' + optional :filepath, type: String, desc: 'Optional path for a direct asset link' + optional :link_type, + type: String, + values: %w[other runbook image package], + default: 'other', + desc: 'The type of the link: `other`, `runbook`, `image`, or `package`. Defaults to `other`' end route_setting :authentication, job_token_allowed: true post 'links' do @@ -60,12 +77,17 @@ module API end params do - requires :link_id, type: String, desc: 'The ID of the link' + requires :link_id, type: Integer, desc: 'The ID of the link' end resource 'links/:link_id' do - desc 'Get a link detail of a release' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'Get a release link' do + detail 'Get an asset as a link from a release. This feature was introduced in GitLab 11.7.' success Entities::Releases::Link + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags release_links_tags end route_setting :authentication, job_token_allowed: true get do @@ -74,15 +96,25 @@ module API present link, with: Entities::Releases::Link end - desc 'Update a link of a release' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'Update a release link' do + detail 'Update an asset as a link from a release. This feature was introduced in GitLab 11.7.' success Entities::Releases::Link + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' } + ] + tags release_links_tags end params do optional :name, type: String, desc: 'The name of the link' optional :url, type: String, desc: 'The URL of the link' - optional :filepath, type: String, desc: 'The filepath of the link' - optional :link_type, type: String, desc: 'The link type' + optional :filepath, type: String, desc: 'Optional path for a direct asset link' + optional :link_type, + type: String, + values: %w[other runbook image package], + default: 'other', + desc: 'The type of the link: `other`, `runbook`, `image`, or `package`. Defaults to `other`' + at_least_one_of :name, :url end route_setting :authentication, job_token_allowed: true @@ -96,9 +128,14 @@ module API end end - desc 'Delete a link of a release' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'Delete a release link' do + detail 'Deletes an asset as a link from a release. This feature was introduced in GitLab 11.7.' success Entities::Releases::Link + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' } + ] + tags release_links_tags end route_setting :authentication, job_token_allowed: true delete do diff --git a/lib/api/releases.rb b/lib/api/releases.rb index cdfcce9dddb..e6884e66200 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -4,6 +4,8 @@ module API class Releases < ::API::Base include PaginationParams + releases_tags = %w[releases] + RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS .merge(tag_name: API::NO_SLASH_URL_PART_REGEX) RELEASE_CLI_USER_AGENT = 'GitLab-release-cli' @@ -12,20 +14,37 @@ module API urgency :low params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the group' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before { authorize_read_group_releases! } - desc 'Get a list of releases for projects in this group.' do + desc 'List group releases' do + detail 'Returns a list of group releases.' success Entities::Release + failure [ + { code: 400, message: 'Bad request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags releases_tags end params do - requires :id, type: Integer, desc: 'The ID of the group to get releases for' - optional :sort, type: String, values: %w[asc desc], default: 'desc', - desc: 'Return projects sorted in ascending and descending order by released_at' - optional :simple, type: Boolean, default: false, - desc: 'Return only the ID, URL, name, and path of each project' + requires :id, + types: [String, Integer], + desc: 'The ID or URL-encoded path of the group owned by the authenticated user' + + optional :sort, + type: String, + values: %w[asc desc], + default: 'desc', + desc: 'The direction of the order. Either `desc` (default) for descending order or `asc` for ascending order' + + optional :simple, + type: Boolean, + default: false, + desc: 'Return only limited fields for each release' use :pagination end @@ -42,26 +61,38 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before { authorize_read_releases! } after { track_release_event } - desc 'Get a project releases' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'List Releases' do + detail 'Returns a paginated list of releases. This feature was introduced in GitLab 11.7.' named 'get_releases' + is_array true success Entities::Release + tags releases_tags end params do use :pagination - optional :order_by, type: String, values: %w[released_at created_at], default: 'released_at', - desc: 'Return releases ordered by `released_at` or `created_at`.' - optional :sort, type: String, values: %w[asc desc], default: 'desc', - desc: 'Return releases sorted in `asc` or `desc` order.' - optional :include_html_description, type: Boolean, - desc: 'If `true`, a response includes HTML rendered markdown of the release description.' + + optional :order_by, + type: String, + values: %w[released_at created_at], + default: 'released_at', + desc: 'The field to use as order. Either `released_at` (default) or `created_at`' + + optional :sort, + type: String, + values: %w[asc desc], + default: 'desc', + desc: 'The direction of the order. Either `desc` (default) for descending order or `asc` for ascending order' + + optional :include_html_description, + type: Boolean, + desc: 'If `true`, a response includes HTML rendered markdown of the release description' end route_setting :authentication, job_token_allowed: true get ':id/releases' do @@ -81,19 +112,26 @@ module API include_html_description: params[:include_html_description] end - desc 'Get a single project release' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'Get a release by a tag name' do + detail 'Gets a release for the given tag. This feature was introduced in GitLab 11.7.' named 'get_release' success Entities::Release + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags releases_tags end params do - requires :tag_name, type: String, desc: 'The name of the tag', as: :tag - optional :include_html_description, type: Boolean, - desc: 'If `true`, a response includes HTML rendered markdown of the release description.' + requires :tag_name, type: String, desc: 'The Git tag the release is associated with', as: :tag + + optional :include_html_description, + type: Boolean, + desc: 'If `true`, a response includes HTML rendered markdown of the release description' end route_setting :authentication, job_token_allowed: true get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do - authorize_download_code! + authorize_read_code! not_found! unless release @@ -103,17 +141,23 @@ module API desc 'Download a project release asset file' do detail 'This feature was introduced in GitLab 15.4.' named 'download_release_asset_file' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags releases_tags end params do - requires :tag_name, type: String, - desc: 'The name of the tag.', as: :tag - requires :file_path, type: String, - file_path: true, - desc: 'The path to the file to download, as specified when creating the release asset.' + requires :tag_name, type: String, desc: 'The Git tag the release is associated with', as: :tag + + requires :file_path, + type: String, + file_path: true, + desc: 'The path to the file to download, as specified when creating the release asset' end route_setting :authentication, job_token_allowed: true get ':id/releases/:tag_name/downloads/*file_path', format: false, requirements: RELEASE_ENDPOINT_REQUIREMENTS do - authorize_download_code! + authorize_read_code! not_found! unless release @@ -127,13 +171,21 @@ module API desc 'Get the latest project release' do detail 'This feature was introduced in GitLab 15.4.' named 'get_latest_release' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags releases_tags end params do - requires :suffix_path, type: String, file_path: true, desc: 'The path to be suffixed to the latest release' + requires :suffix_path, + type: String, + file_path: true, + desc: 'The path to be suffixed to the latest release' end route_setting :authentication, job_token_allowed: true get ':id/releases/permalink/latest(/)(*suffix_path)', format: false, requirements: RELEASE_ENDPOINT_REQUIREMENTS do - authorize_download_code! + authorize_read_code! # Try to find the latest release latest_release = find_latest_release @@ -156,27 +208,50 @@ module API redirect redirect_url end - desc 'Create a new release' do - detail 'This feature was introduced in GitLab 11.7.' + desc 'Create a release' do + detail 'Creates a release. Developer level access to the project is required to create a release. This feature was introduced in GitLab 11.7.' named 'create_release' success Entities::Release + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Conflict' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags releases_tags end params do - requires :tag_name, type: String, desc: 'The name of the tag', as: :tag + requires :tag_name, type: String, desc: 'The tag where the release is created from', as: :tag optional :tag_message, type: String, desc: 'Message to use if creating a new annotated tag' - optional :name, type: String, desc: 'The name of the release' - optional :description, type: String, desc: 'The release notes' - optional :ref, type: String, desc: 'Commit SHA or branch name to use if creating a new tag' + optional :name, type: String, desc: 'The release name' + optional :description, type: String, desc: 'The description of the release. You can use Markdown' + + optional :ref, + type: String, + desc: "If a tag specified in `tag_name` doesn't exist, the release is created from `ref` and tagged " \ + "with `tag_name`. It can be a commit SHA, another tag name, or a branch name." + optional :assets, type: Hash do optional :links, type: Array do - requires :name, type: String, desc: 'The name of the link' - requires :url, type: String, desc: 'The URL of the link' - optional :filepath, type: String, desc: 'The filepath of the link' - optional :link_type, type: String, desc: 'The link type, one of: "runbook", "image", "package" or "other"' + requires :name, type: String, desc: 'The name of the link. Link names must be unique within the release' + requires :url, type: String, desc: 'The URL of the link. Link URLs must be unique within the release' + optional :filepath, type: String, desc: 'Optional path for a direct asset link' + optional :link_type, type: String, desc: 'The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`' end end - optional :milestones, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The titles of the related milestones', default: [] - optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.' + + optional :milestones, + type: Array[String], + coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + desc: 'The title of each milestone the release is associated with. GitLab Premium customers can specify group milestones', + default: [] + + optional :released_at, + type: DateTime, + desc: 'Date and time for the release. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). ' \ + 'Only provide this field if creating an upcoming or historical release.' end route_setting :authentication, job_token_allowed: true post ':id/releases' do @@ -196,16 +271,27 @@ module API end desc 'Update a release' do - detail 'This feature was introduced in GitLab 11.7.' + detail 'Updates a release. Developer level access to the project is required to update a release. This feature was introduced in GitLab 11.7.' named 'update_release' success Entities::Release + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags releases_tags end params do - requires :tag_name, type: String, desc: 'The name of the tag', as: :tag - optional :name, type: String, desc: 'The name of the release' - optional :description, type: String, desc: 'Release notes with markdown support' - optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready.' - optional :milestones, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The titles of the related milestones' + requires :tag_name, type: String, desc: 'The Git tag the release is associated with', as: :tag + optional :name, type: String, desc: 'The release name' + optional :description, type: String, desc: 'The description of the release. You can use Markdown' + optional :released_at, type: DateTime, desc: 'The date when the release is/was ready. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`)' + + optional :milestones, + type: Array[String], + coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + desc: 'The title of each milestone to associate with the release. GitLab Premium customers can specify group milestones. To remove all milestones from the release, specify `[]`' end route_setting :authentication, job_token_allowed: true put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do @@ -226,12 +312,19 @@ module API end desc 'Delete a release' do - detail 'This feature was introduced in GitLab 11.7.' + detail "Delete a release. Deleting a release doesn't delete the associated tag. Maintainer level access to the project is required to delete a release. This feature was introduced in GitLab 11.7." named 'delete_release' success Entities::Release + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags releases_tags end params do - requires :tag_name, type: String, desc: 'The name of the tag', as: :tag + requires :tag_name, type: String, desc: 'The Git tag the release is associated with', as: :tag end route_setting :authentication, job_token_allowed: true delete ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do @@ -280,6 +373,10 @@ module API authorize! :download_code, user_project end + def authorize_read_code! + authorize! :read_code, user_project + end + def authorize_create_evidence! # extended in EE end diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb index 8de155312fb..f7ea5a6ad2b 100644 --- a/lib/api/remote_mirrors.rb +++ b/lib/api/remote_mirrors.rb @@ -11,11 +11,17 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc "List the project's remote mirrors" do - success Entities::RemoteMirror + success code: 200, model: Entities::RemoteMirror + is_array true + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[remote_mirrors] end params do use :pagination @@ -26,7 +32,12 @@ module API end desc 'Get a single remote mirror' do - success Entities::RemoteMirror + success code: 200, model: Entities::RemoteMirror + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[remote_mirrors] end params do requires :mirror_id, type: String, desc: 'The ID of a remote mirror' @@ -38,13 +49,21 @@ module API end desc 'Create remote mirror for a project' do - success Entities::RemoteMirror + success code: 201, model: Entities::RemoteMirror + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[remote_mirrors] end params do - requires :url, type: String, desc: 'The URL for a remote mirror' - optional :enabled, type: Boolean, desc: 'Determines if the mirror is enabled' - optional :only_protected_branches, type: Boolean, desc: 'Determines if only protected branches are mirrored' - optional :keep_divergent_refs, type: Boolean, desc: 'Determines if divergent refs are kept on the target' + requires :url, type: String, desc: 'The URL for a remote mirror', documentation: { example: 'https://*****:*****@example.com/gitlab/example.git' } + optional :enabled, type: Boolean, desc: 'Determines if the mirror is enabled', documentation: { example: false } + optional :only_protected_branches, type: Boolean, desc: 'Determines if only protected branches are mirrored', + documentation: { example: false } + optional :keep_divergent_refs, type: Boolean, desc: 'Determines if divergent refs are kept on the target', + documentation: { example: false } end post ':id/remote_mirrors' do create_params = declared_params(include_missing: false) @@ -59,13 +78,21 @@ module API end desc 'Update the attributes of a single remote mirror' do - success Entities::RemoteMirror + success code: 200, model: Entities::RemoteMirror + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[remote_mirrors] end params do requires :mirror_id, type: String, desc: 'The ID of a remote mirror' - optional :enabled, type: Boolean, desc: 'Determines if the mirror is enabled' - optional :only_protected_branches, type: Boolean, desc: 'Determines if only protected branches are mirrored' - optional :keep_divergent_refs, type: Boolean, desc: 'Determines if divergent refs are kept on the target' + optional :enabled, type: Boolean, desc: 'Determines if the mirror is enabled', documentation: { example: true } + optional :only_protected_branches, type: Boolean, desc: 'Determines if only protected branches are mirrored', + documentation: { example: false } + optional :keep_divergent_refs, type: Boolean, desc: 'Determines if divergent refs are kept on the target', + documentation: { example: false } end put ':id/remote_mirrors/:mirror_id' do mirror = user_project.remote_mirrors.find(params[:mirror_id]) @@ -88,6 +115,13 @@ module API desc 'Delete a single remote mirror' do detail 'This feature was introduced in GitLab 14.10' + success code: 204 + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[remote_mirrors] end params do requires :mirror_id, type: String, desc: 'The ID of a remote mirror' diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index c6a2d582d8a..70535496b12 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -15,33 +15,40 @@ module API requires :version, type: String, regexp: Gitlab::Regex.unbounded_semver_regex, - desc: 'The version of the release, using the semantic versioning format' + desc: 'The version of the release, using the semantic versioning format', + documentation: { example: '1.0.0' } optional :from, type: String, - desc: 'The first commit in the range of commits to use for the changelog' + desc: 'The first commit in the range of commits to use for the changelog', + documentation: { example: 'ed899a2f4b50b4370feeea94676502b42383c746' } optional :to, type: String, - desc: 'The last commit in the range of commits to use for the changelog' + desc: 'The last commit in the range of commits to use for the changelog', + documentation: { example: '6104942438c14ec7bd21c6cd5bd995272b3faff6' } optional :date, type: DateTime, - desc: 'The date and time of the release' + desc: 'The date and time of the release', + documentation: { type: 'dateTime', example: '2021-09-20T11:50:22.001+00:00' } optional :trailer, type: String, desc: 'The Git trailer to use for determining if commits are to be included in the changelog', - default: ::Repositories::ChangelogService::DEFAULT_TRAILER + default: ::Repositories::ChangelogService::DEFAULT_TRAILER, + documentation: { example: 'Changelog' } end end - before { authorize! :download_code, user_project } + before { authorize! :read_code, user_project } feature_category :source_code_management params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], + desc: 'The ID or URL-encoded path of the project', + documentation: { example: 1 } end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do helpers do @@ -56,7 +63,7 @@ module API end def assign_blob_vars!(limit:) - authorize! :download_code, user_project + authorize! :read_code, user_project @repo = user_project.repository @@ -94,15 +101,19 @@ module API success Entities::TreeObject end params do - optional :ref, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used' - optional :path, type: String, desc: 'The path of the tree' + optional :ref, type: String, + desc: 'The name of a repository branch or tag, if not given the default branch is used', + documentation: { example: 'main' } + optional :path, type: String, desc: 'The path of the tree', documentation: { example: 'files/html' } optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree' use :pagination optional :pagination, type: String, values: %w(legacy keyset none), default: 'legacy', desc: 'Specify the pagination method ("none" is only valid if "recursive" is true)' given pagination: ->(value) { value == 'keyset' } do - optional :page_token, type: String, desc: 'Record from which to start the keyset pagination' + optional :page_token, type: String, + desc: 'Record from which to start the keyset pagination', + documentation: { example: 'a1e8f8d745cc87e3a9248358d9352bb7f9a0aeba' } end given pagination: ->(value) { value == 'none' } do @@ -123,7 +134,8 @@ module API desc 'Get raw blob contents from the repository' params do - requires :sha, type: String, desc: 'The commit hash' + requires :sha, type: String, + desc: 'The commit hash', documentation: { example: '7d70e02340bac451f281cecf0a980907974bd8be' } end get ':id/repository/blobs/:sha/raw' do # Load metadata enough to ask Workhorse to load the whole blob @@ -136,7 +148,8 @@ module API desc 'Get a blob from the repository' params do - requires :sha, type: String, desc: 'The commit hash' + requires :sha, type: String, + desc: 'The commit hash', documentation: { example: '7d70e02340bac451f281cecf0a980907974bd8be' } end get ':id/repository/blobs/:sha' do assign_blob_vars!(limit: -1) @@ -151,9 +164,12 @@ module API desc 'Get an archive of the repository' params do - optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded' - optional :format, type: String, desc: 'The archive format' - optional :path, type: String, desc: 'Subfolder of the repository to be downloaded' + optional :sha, type: String, + desc: 'The commit sha of the archive to be downloaded', + documentation: { example: '7d70e02340bac451f281cecf0a980907974bd8be' } + optional :format, type: String, desc: 'The archive format', documentation: { example: 'tar.gz' } + optional :path, type: String, + desc: 'Subfolder of the repository to be downloaded', documentation: { example: 'files/archives' } end get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do check_archive_rate_limit!(current_user, user_project) do @@ -171,9 +187,13 @@ module API success Entities::Compare end params do - requires :from, type: String, desc: 'The commit, branch name, or tag name to start comparison' - requires :to, type: String, desc: 'The commit, branch name, or tag name to stop comparison' - optional :from_project_id, type: String, desc: 'The project to compare from' + requires :from, type: String, + desc: 'The commit, branch name, or tag name to start comparison', + documentation: { example: 'main' } + requires :to, type: String, + desc: 'The commit, branch name, or tag name to stop comparison', + documentation: { example: 'feature' } + optional :from_project_id, type: Integer, desc: 'The project to compare from', documentation: { example: 1 } optional :straight, type: Boolean, desc: 'Comparison method, `true` for direct comparison between `from` and `to` (`from`..`to`), `false` to compare using merge base (`from`...`to`)', default: false end get ':id/repository/compare', urgency: :low do @@ -215,7 +235,10 @@ module API success Entities::Commit end params do - requires :refs, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce + requires :refs, type: Array[String], + coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + desc: 'The refs to find the common ancestor of, multiple refs can be passed', + documentation: { example: 'main' } end get ':id/repository/merge_base' do refs = params[:refs] @@ -241,12 +264,14 @@ module API desc 'Generates a changelog section for a release and returns it' do detail 'This feature was introduced in GitLab 14.6' + success Entities::Changelog end params do use :release_params optional :config_file, type: String, + documentation: { example: '.gitlab/changelog_config.yml' }, desc: "The file path to the configuration file as stored in the project's Git repository. Defaults to '.gitlab/changelog_config.yml'" end get ':id/repository/changelog' do @@ -264,26 +289,31 @@ module API desc 'Generates a changelog section for a release and commits it in a changelog file' do detail 'This feature was introduced in GitLab 13.9' + success code: 200 end params do use :release_params optional :branch, type: String, - desc: 'The branch to commit the changelog changes to' + desc: 'The branch to commit the changelog changes to', + documentation: { example: 'main' } optional :config_file, type: String, + documentation: { example: '.gitlab/changelog_config.yml' }, desc: "The file path to the configuration file as stored in the project's Git repository. Defaults to '.gitlab/changelog_config.yml'" optional :file, type: String, desc: 'The file to commit the changelog changes to', - default: ::Repositories::ChangelogService::DEFAULT_FILE + default: ::Repositories::ChangelogService::DEFAULT_FILE, + documentation: { example: 'CHANGELOG.md' } optional :message, type: String, - desc: 'The commit message to use when committing the changelog' + desc: 'The commit message to use when committing the changelog', + documentation: { example: 'Initial commit' } end post ':id/repository/changelog' do branch = params[:branch] || user_project.default_branch_or_main diff --git a/lib/api/resource_access_tokens.rb b/lib/api/resource_access_tokens.rb index 2ba109b7092..754dfadb5fc 100644 --- a/lib/api/resource_access_tokens.rb +++ b/lib/api/resource_access_tokens.rb @@ -4,6 +4,8 @@ module API class ResourceAccessTokens < ::API::Base include PaginationParams + ALLOWED_RESOURCE_ACCESS_LEVELS = Gitlab::Access.options_with_owner.freeze + before { authenticate! } feature_category :authentication_and_authorization @@ -12,9 +14,12 @@ module API resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get list of all access tokens for the specified resource' do detail 'This feature was introduced in GitLab 13.9.' + is_array true + tags ["#{source_type}_access_tokens"] + success Entities::ResourceAccessToken end params do - requires :id, type: String, desc: "The #{source_type} ID" + requires :id, types: [String, Integer], desc: "ID or URL-encoded path of the #{source_type}" end get ":id/access_tokens" do resource = find_source(source_type, params[:id]) @@ -29,9 +34,11 @@ module API desc 'Get an access token for the specified resource by ID' do detail 'This feature was introduced in GitLab 14.10.' + tags ["#{source_type}_access_tokens"] + success Entities::ResourceAccessToken end params do - requires :id, type: String, desc: "The #{source_type} ID" + requires :id, types: [String, Integer], desc: "ID or URL-encoded path of the #{source_type}" requires :token_id, type: String, desc: "The ID of the token" end get ":id/access_tokens/:token_id" do @@ -51,6 +58,12 @@ module API desc 'Revoke a resource access token' do detail 'This feature was introduced in GitLab 13.9.' + tags ["#{source_type}_access_tokens"] + success code: 204 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 404, message: 'Not found' } + ] end params do requires :id, type: String, desc: "The #{source_type} ID" @@ -75,13 +88,21 @@ module API desc 'Create a resource access token' do detail 'This feature was introduced in GitLab 13.9.' + tags ["#{source_type}_access_tokens"] + success Entities::ResourceAccessTokenWithToken end params do - requires :id, type: String, desc: "The #{source_type} ID" - requires :name, type: String, desc: "Resource access token name" - requires :scopes, type: Array[String], desc: "The permissions of the token" - optional :access_level, type: Integer, desc: "The access level of the token in the #{source_type}" - optional :expires_at, type: Date, desc: "The expiration date of the token" + requires :id, type: String, desc: "The #{source_type} ID", documentation: { example: 2 } + requires :name, type: String, desc: "Resource access token name", documentation: { example: 'test' } + requires :scopes, type: Array[String], values: ::Gitlab::Auth.resource_bot_scopes.map(&:to_s), + desc: "The permissions of the token", + documentation: { example: %w[api read_repository] } + optional :access_level, type: Integer, + values: ALLOWED_RESOURCE_ACCESS_LEVELS.values, + default: Gitlab::Access::MAINTAINER, + desc: "The access level of the token in the #{source_type}", + documentation: { example: 40 } + optional :expires_at, type: Date, desc: "The expiration date of the token", documentation: { example: '"2021-01-31' } end post ':id/access_tokens' do resource = find_source(source_type, params[:id]) diff --git a/lib/api/resource_milestone_events.rb b/lib/api/resource_milestone_events.rb index 04d71faa56a..5640e88ae6e 100644 --- a/lib/api/resource_milestone_events.rb +++ b/lib/api/resource_milestone_events.rb @@ -5,6 +5,8 @@ module API include PaginationParams helpers ::API::Helpers::NotesHelpers + resource_milestone_events_tags = %w[resource_milestone_events] + before { authenticate! } { @@ -15,17 +17,19 @@ module API eventables_str = eventable_type.to_s.underscore.pluralize params do - requires :id, type: String, desc: "The ID of a #{parent_type}" + requires :id, types: [String, Integer], desc: "The ID or URL-encoded path of the #{parent_type}" end resource parent_type.pluralize.to_sym, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc "Get a list of #{eventable_type.to_s.downcase} resource milestone events" do + desc "List project #{eventable_type.underscore.humanize} milestone events" do + detail "Gets a list of all milestone events for a single #{eventable_type.underscore.humanize}" success Entities::ResourceMilestoneEvent + is_array true + tags resource_milestone_events_tags end params do requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable' use :pagination end - get ":id/#{eventables_str}/:eventable_id/resource_milestone_events", feature_category: feature_category, urgency: :low do eventable = find_noteable(eventable_type, params[:eventable_id]) @@ -34,8 +38,13 @@ module API present paginate(events), with: Entities::ResourceMilestoneEvent end - desc "Get a single #{eventable_type.to_s.downcase} resource milestone event" do + desc "Get single #{eventable_type.underscore.humanize} milestone event" do + detail "Returns a single milestone event for a specific project #{eventable_type.underscore.humanize}" success Entities::ResourceMilestoneEvent + failure [ + { code: 404, message: 'Not found' } + ] + tags resource_milestone_events_tags end params do requires :event_id, type: String, desc: 'The ID of a resource milestone event' diff --git a/lib/api/rpm_project_packages.rb b/lib/api/rpm_project_packages.rb index d17470ae92d..40b8d022c6c 100644 --- a/lib/api/rpm_project_packages.rb +++ b/lib/api/rpm_project_packages.rb @@ -21,7 +21,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do namespace ':id/packages/rpm' do @@ -30,7 +30,14 @@ module API requires :file_name, type: String, desc: 'Repository metadata file name' end get 'repodata/*file_name', requirements: { file_name: API::NO_SLASH_URL_PART_REGEX } do - not_found! + authorize_read_package!(authorized_user_project) + + repository_file = Packages::Rpm::RepositoryFile.find_by_project_id_and_file_name!( + authorized_user_project.id, + "#{params['file_name']}.#{params['format']}" + ) + + present_carrierwave_file!(repository_file.file) end desc 'Download RPM package files' @@ -39,6 +46,13 @@ module API requires :file_name, type: String, desc: 'RPM package file name' end get '*package_file_id/*file_name', requirements: { file_name: API::NO_SLASH_URL_PART_REGEX } do + track_package_event( + 'pull_package', + :rpm, + category: self.class.name, + project: authorized_user_project, + namespace: authorized_user_project.namespace + ) not_found! end @@ -50,6 +64,15 @@ module API bad_request!('File is too large') end + track_package_event( + 'push_package', + :rpm, + user: current_user, + category: self.class.name, + project: authorized_user_project, + namespace: authorized_user_project.namespace + ) + not_found! end diff --git a/lib/api/rubygem_packages.rb b/lib/api/rubygem_packages.rb index b4d02613e4c..87cf1f66223 100644 --- a/lib/api/rubygem_packages.rb +++ b/lib/api/rubygem_packages.rb @@ -93,7 +93,7 @@ module API detail 'This feature was introduced in GitLab 13.9' end params do - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end post 'gems' do authorize_upload!(user_project) diff --git a/lib/api/search.rb b/lib/api/search.rb index ff17696ed3e..cf6a1385783 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -67,8 +67,8 @@ module API Gitlab::Metrics::GlobalSearchSlis.record_apdex( elapsed: @search_duration_s, - search_type: search_type, - search_level: search_service.level, + search_type: search_type(additional_params), + search_level: search_service(additional_params).level, search_scope: search_scope ) @@ -81,7 +81,7 @@ module API # with a 200 status code, but an empty @search_duration_s. Gitlab::Metrics::GlobalSearchSlis.record_error_rate( error: @search_duration_s.nil? || (status < 200 || status >= 400), - search_type: search_type, + search_type: search_type(additional_params), search_level: search_service(additional_params).level, search_scope: search_scope ) @@ -171,7 +171,7 @@ module API detail 'This feature was introduced in GitLab 10.5.' end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' requires :search, type: String, desc: 'The expression it should be searched for' requires :scope, type: String, diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 8c8b6c0a1ba..26b7e58bc7a 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -53,6 +53,7 @@ module API optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility' optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects' optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility' + optional :disable_admin_oauth_scopes, type: Boolean, desc: 'Stop administrators from connecting to non-trusted OAuth applications.' optional :disable_feed_token, type: Boolean, desc: 'Disable display of RSS/Atom and Calendar `feed_tokens`' optional :disabled_oauth_sign_in_sources, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Disable certain OAuth sign-in sources' optional :domain_denylist_enabled, type: Boolean, desc: 'Enable domain denylist for sign ups' diff --git a/lib/api/snippet_repository_storage_moves.rb b/lib/api/snippet_repository_storage_moves.rb index e3034191641..92eb10b3bb8 100644 --- a/lib/api/snippet_repository_storage_moves.rb +++ b/lib/api/snippet_repository_storage_moves.rb @@ -11,7 +11,8 @@ module API resource :snippet_repository_storage_moves do desc 'Get a list of all snippet repository storage moves' do detail 'This feature was introduced in GitLab 13.8.' - success Entities::Snippets::RepositoryStorageMove + is_array true + success code: 200, model: Entities::Snippets::RepositoryStorageMove end params do use :pagination @@ -24,7 +25,7 @@ module API desc 'Get a snippet repository storage move' do detail 'This feature was introduced in GitLab 13.8.' - success Entities::Snippets::RepositoryStorageMove + success code: 200, model: Entities::Snippets::RepositoryStorageMove end params do requires :repository_storage_move_id, type: Integer, desc: 'The ID of a snippet repository storage move' @@ -37,6 +38,7 @@ module API desc 'Schedule bulk snippet repository storage moves' do detail 'This feature was introduced in GitLab 13.8.' + success code: 202 end params do requires :source_storage_name, type: String, desc: 'The source storage shard', values: -> { Gitlab.config.repositories.storages.keys } @@ -68,7 +70,8 @@ module API desc 'Get a list of all snippets repository storage moves' do detail 'This feature was introduced in GitLab 13.8.' - success Entities::Snippets::RepositoryStorageMove + is_array true + success code: 200, model: Entities::Snippets::RepositoryStorageMove end params do use :pagination @@ -81,7 +84,7 @@ module API desc 'Get a snippet repository storage move' do detail 'This feature was introduced in GitLab 13.8.' - success Entities::Snippets::RepositoryStorageMove + success code: 200, model: Entities::Snippets::RepositoryStorageMove end params do requires :repository_storage_move_id, type: Integer, desc: 'The ID of a snippet repository storage move' @@ -94,14 +97,14 @@ module API desc 'Schedule a snippet repository storage move' do detail 'This feature was introduced in GitLab 13.8.' - success Entities::Snippets::RepositoryStorageMove + success code: 201, model: Entities::Snippets::RepositoryStorageMove end params do optional :destination_storage_name, type: String, desc: 'The destination storage shard' end post ':id/repository_storage_moves' do storage_move = user_snippet.repository_storage_moves.build( - declared_params.merge(source_storage_name: user_snippet.repository_storage) + declared_params.compact.merge(source_storage_name: user_snippet.repository_storage) ) if storage_move.schedule diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb index 5f8e6c806cb..36698a220bd 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -28,6 +28,11 @@ module API desc 'Get a snippets list for an authenticated user' do detail 'This feature was introduced in GitLab 8.15.' success Entities::Snippet + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[snippets] + is_array true end params do optional :created_after, type: DateTime, desc: 'Return snippets created after the specified time' @@ -45,6 +50,11 @@ module API desc 'List all public personal snippets current_user has access to' do detail 'This feature was introduced in GitLab 8.15.' success Entities::PersonalSnippet + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[snippets] + is_array true end params do optional :created_after, type: DateTime, desc: 'Return snippets created after the specified time' @@ -62,6 +72,10 @@ module API desc 'Get a single snippet' do detail 'This feature was introduced in GitLab 8.15.' success Entities::PersonalSnippet + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[snippets] end params do requires :id, type: Integer, desc: 'The ID of a snippet' @@ -77,6 +91,12 @@ module API desc 'Create new snippet' do detail 'This feature was introduced in GitLab 8.15.' success Entities::PersonalSnippet + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[snippets] end params do requires :title, type: String, allow_blank: false, desc: 'The title of a snippet' @@ -110,6 +130,12 @@ module API desc 'Update an existing snippet' do detail 'This feature was introduced in GitLab 8.15.' success Entities::PersonalSnippet + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[snippets] end params do @@ -154,6 +180,11 @@ module API desc 'Remove snippet' do detail 'This feature was introduced in GitLab 8.15.' success Entities::PersonalSnippet + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' } + ] + tags %w[snippets] end params do requires :id, type: Integer, desc: 'The ID of a snippet' @@ -178,6 +209,10 @@ module API desc 'Get a raw snippet' do detail 'This feature was introduced in GitLab 8.15.' + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[snippets] end params do requires :id, type: Integer, desc: 'The ID of a snippet' @@ -189,7 +224,12 @@ module API present content_for(snippet) end - desc 'Get raw snippet file contents from the repository' + desc 'Get raw snippet file contents from the repository' do + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[snippets] + end params do use :raw_file_params end @@ -202,6 +242,10 @@ module API desc 'Get the user agent details for a snippet' do success Entities::UserAgentDetail + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[snippets] end params do requires :id, type: Integer, desc: 'The ID of a snippet' diff --git a/lib/api/statistics.rb b/lib/api/statistics.rb index a12a2ed08d7..1af83c0737a 100644 --- a/lib/api/statistics.rb +++ b/lib/api/statistics.rb @@ -10,7 +10,7 @@ module API MergeRequest, Note, Snippet, Key, Milestone].freeze desc 'Get the current application statistics' do - success Entities::ApplicationStatistics + success code: 200, model: Entities::ApplicationStatistics end get "application/statistics", urgency: :low do counts = Gitlab::Database::Count.approximate_counts(COUNTED_ITEMS) diff --git a/lib/api/submodules.rb b/lib/api/submodules.rb index 2b51ab91c40..6638ac57f69 100644 --- a/lib/api/submodules.rb +++ b/lib/api/submodules.rb @@ -18,17 +18,34 @@ module API end params do - requires :id, type: String, desc: 'The project ID' + requires :id, + type: String, + desc: 'The ID or URL-encoded path of a project', + documentation: { example: 'gitlab-org/gitlab' } end - resource :projects, requirements: Files::FILE_ENDPOINT_REQUIREMENTS do + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Update existing submodule reference in repository' do - success Entities::Commit + success code: 200, model: Entities::CommitDetail + failure [ + { code: 404, message: '404 Project Not Found' }, + { code: 401, message: '401 Unauthorized' }, + { code: 400, message: 'The repository is empty' } + ] end params do - requires :submodule, type: String, desc: 'Url encoded full path to submodule.' - requires :commit_sha, type: String, desc: 'Commit sha to update the submodule to.' - requires :branch, type: String, desc: 'Name of the branch to commit into.' - optional :commit_message, type: String, desc: 'Commit message. If no message is provided a default one will be set.' + requires :submodule, + type: String, + desc: 'Url encoded full path to submodule.', + documentation: { example: 'gitlab-org/gitlab-shell' } + requires :commit_sha, + type: String, + desc: 'Commit sha to update the submodule to.', + documentation: { example: 'ed899a2f4b50b4370feeea94676502b42383c746' } + requires :branch, type: String, desc: 'Name of the branch to commit into.', documentation: { example: 'main' } + optional :commit_message, + type: String, + desc: 'Commit message. If no message is provided a default one will be set.', + documentation: { example: 'Commit message' } end put ":id/repository/submodules/:submodule", requirements: Files::FILE_ENDPOINT_REQUIREMENTS do authorize! :push_code, user_project diff --git a/lib/api/suggestions.rb b/lib/api/suggestions.rb index 0697169b49a..6260983087f 100644 --- a/lib/api/suggestions.rb +++ b/lib/api/suggestions.rb @@ -9,9 +9,10 @@ module API resource :suggestions do desc 'Apply suggestion patch in the Merge Request it was created' do success Entities::Suggestion + tags %w[suggestions] end params do - requires :id, type: String, desc: 'The suggestion ID' + requires :id, type: Integer, desc: 'The ID of the suggestion' optional :commit_message, type: String, desc: "A custom commit message to use instead of the default generated message or the project's default message" end put ':id/apply', urgency: :low do @@ -26,9 +27,10 @@ module API desc 'Apply multiple suggestion patches in the Merge Request where they were created' do success Entities::Suggestion + tags %w[suggestions] end params do - requires :ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: "An array of suggestion ID's" + requires :ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: "An array of the suggestion IDs" optional :commit_message, type: String, desc: "A custom commit message to use instead of the default generated message or the project's default message" end put 'batch_apply', urgency: :low do diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index 804cedfefe9..f2019d785a0 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -4,6 +4,8 @@ module API class SystemHooks < ::API::Base include PaginationParams + system_hooks_tags = %w[system_hooks] + feature_category :integrations before do @@ -19,12 +21,13 @@ module API end params :hook_parameters do - optional :token, type: String, desc: 'The token used to validate payloads' - optional :push_events, type: Boolean, desc: "Trigger hook on push events" - optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events" - optional :merge_requests_events, type: Boolean, desc: "Trigger hook on tag push events" - optional :repository_update_events, type: Boolean, desc: "Trigger hook on repository update events" - optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook" + optional :token, type: String, + desc: "Secret token to validate received payloads; this isn't returned in the response" + optional :push_events, type: Boolean, desc: 'When true, the hook fires on push events' + optional :tag_push_events, type: Boolean, desc: 'When true, the hook fires on new tags being pushed' + optional :merge_requests_events, type: Boolean, desc: 'Trigger hook on merge requests events' + optional :repository_update_events, type: Boolean, desc: 'Trigger hook on repository update events' + optional :enable_ssl_verification, type: Boolean, desc: 'Do SSL verification when triggering the hook' use :url_variables end end @@ -32,8 +35,11 @@ module API resource :hooks do mount ::API::Hooks::UrlVariables - desc 'Get the list of system hooks' do + desc 'List system hooks' do + detail 'Get a list of all system hooks' success Entities::Hook + is_array true + tags system_hooks_tags end params do use :pagination @@ -42,8 +48,13 @@ module API present paginate(SystemHook.all), with: Entities::Hook end - desc 'Get a hook' do + desc 'Get system hook' do + detail 'Get a system hook by its ID. Introduced in GitLab 14.9.' success Entities::Hook + failure [ + { code: 404, message: 'Not found' } + ] + tags system_hooks_tags end params do requires :hook_id, type: Integer, desc: 'The ID of the system hook' @@ -52,8 +63,15 @@ module API present find_hook, with: Entities::Hook end - desc 'Create a new system hook' do + desc 'Add new system hook' do + detail 'Add a new system hook' success Entities::Hook + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags system_hooks_tags end params do use :requires_url @@ -66,11 +84,18 @@ module API save_hook(hook, Entities::Hook) end - desc 'Update an existing system hook' do + desc 'Edit system hook' do + detail 'Edits a system hook' success Entities::Hook + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags system_hooks_tags end params do - requires :hook_id, type: Integer, desc: "The ID of the hook to update" + requires :hook_id, type: Integer, desc: 'The ID of the system hook' use :optional_url use :hook_parameters end @@ -90,8 +115,13 @@ module API kind: 'system_hooks' } - desc 'Delete a hook' do + desc 'Delete system hook' do + detail 'Deletes a system hook' success Entities::Hook + failure [ + { code: 404, message: 'Not found' } + ] + tags system_hooks_tags end params do requires :hook_id, type: Integer, desc: 'The ID of the system hook' diff --git a/lib/api/tags.rb b/lib/api/tags.rb index c8ac68189f5..b412a17bc6f 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -7,17 +7,25 @@ module API TAG_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(tag_name: API::NO_SLASH_URL_PART_REGEX) before do - authorize! :download_code, user_project + authorize! :read_code, user_project not_found! unless user_project.repo_exists? end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a project repository tags' do - success Entities::Tag + is_array true + success code: 200, model: Entities::Tag + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' }, + { code: 503, message: 'Service unavailable' } + ] + tags %w[tags] end params do optional :sort, type: String, values: %w[asc desc], default: 'desc', @@ -46,7 +54,12 @@ module API end desc 'Get a single repository tag' do - success Entities::Tag + success code: 200, model: Entities::Tag + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[tags] end params do requires :tag_name, type: String, desc: 'The name of the tag' @@ -59,12 +72,18 @@ module API end desc 'Create a new repository tag' do - success Entities::Tag + success code: 201, model: Entities::Tag + failure [ + { code: 400, message: 'Bad request' }, + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[tags] end params do - requires :tag_name, type: String, desc: 'The name of the tag' - requires :ref, type: String, desc: 'The commit sha or branch name' - optional :message, type: String, desc: 'Specifying a message creates an annotated tag' + requires :tag_name, type: String, desc: 'The name of the tag', documentation: { example: 'v.1.0.0' } + requires :ref, type: String, desc: 'The commit sha or branch name', documentation: { example: '2695effb5807a22ff3d138d593fd856244e155e7' } + optional :message, type: String, desc: 'Specifying a message creates an annotated tag', documentation: { example: 'Release 1.0.0' } end post ':id/repository/tags', :release_orchestration do authorize_admin_tag @@ -81,7 +100,15 @@ module API end end - desc 'Delete a repository tag' + desc 'Delete a repository tag' do + success code: 204 + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' }, + { code: 412, message: 'Precondition failed' } + ] + tags %w[tags] + end params do requires :tag_name, type: String, desc: 'The name of the tag' end diff --git a/lib/api/terraform/modules/v1/packages.rb b/lib/api/terraform/modules/v1/packages.rb index 267d41e5fb9..5624784228e 100644 --- a/lib/api/terraform/modules/v1/packages.rb +++ b/lib/api/terraform/modules/v1/packages.rb @@ -21,7 +21,7 @@ module API module_version: SEMVER_REGEX }.freeze - feature_category :infrastructure_as_code + feature_category :package_registry urgency :low after_validation do @@ -92,11 +92,29 @@ module API authorize_read_package!(package || module_namespace) end + desc 'List versions for a module' do + detail 'List versions for a module' + success code: 200, model: Entities::Terraform::ModuleVersions + failure [ + { code: 403, message: 'Forbidden' } + ] + is_array true + tags %w[terraform_registry] + end get 'versions' do presenter = ::Terraform::ModulesPresenter.new(packages, params[:module_system]) present presenter, with: ::API::Entities::Terraform::ModuleVersions end + desc 'Get download location for the latest version of a module' do + detail 'Download the latest version of a module' + success code: 302 + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[terraform_registry] + end get 'download' do latest_version = packages.order_version.last&.version @@ -115,6 +133,15 @@ module API redirect(download_path) end + desc 'Get details about the latest version of a module' do + detail 'Get details about the latest version of a module' + success code: 200, model: Entities::Terraform::ModuleVersion + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[terraform_registry] + end get do latest_package = packages.order_version.last @@ -133,6 +160,15 @@ module API not_found! unless package && package_file end + desc 'Get download location for specific version of a module' do + detail 'Download specific version of a module' + success code: 204 + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[terraform_registry] + end get 'download' do module_file_path = api_v4_packages_terraform_modules_v1_module_version_file_path( module_namespace: params[:module_namespace], @@ -154,6 +190,15 @@ module API accept.token_types(:deploy_token_from_jwt, :job_token_from_jwt, :personal_access_token_from_jwt).sent_through(:token_param) end + desc 'Download specific version of a module' do + detail 'Download specific version of a module' + success File + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[terraform_registry] + end get do track_package_event('pull_package', :terraform_module, project: package.project, namespace: module_namespace, user: current_user) @@ -166,6 +211,15 @@ module API # format: false is required, otherwise grape splits the semver version into 2 params: # params[:module_version] and params[:format], # thus leading to an invalid/not found module version + desc 'Get details about specific version of a module' do + detail 'Get details about specific version of a module' + success code: 200, model: Entities::Terraform::ModuleVersion + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[terraform_registry] + end get format: false do presenter = ::Terraform::ModuleVersionPresenter.new(package, params[:module_system]) present presenter, with: ::API::Entities::Terraform::ModuleVersion @@ -189,6 +243,11 @@ module API desc 'Workhorse authorize Terraform Module package file' do detail 'This feature was introduced in GitLab 13.11' + success code: 200 + failure [ + { code: 403, message: 'Forbidden' } + ] + tags %w[terraform_registry] end put 'authorize' do @@ -200,10 +259,19 @@ module API desc 'Upload Terraform Module package file' do detail 'This feature was introduced in GitLab 13.11' + success code: 201 + failure [ + { code: 400, message: 'Invalid file' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + consumes %w[multipart/form-data] + tags %w[terraform_registry] end params do - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end put do diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb index a19919b5e76..577d011ebad 100644 --- a/lib/api/terraform/state.rb +++ b/lib/api/terraform/state.rb @@ -27,13 +27,21 @@ module API increment_unique_values('p_terraform_state_api_unique_users', current_user.id) if Feature.enabled?(:route_hll_to_snowplow_phase2, user_project&.namespace) - Gitlab::Tracking.event('API::Terraform::State', 'p_terraform_state_api_unique_users', - namespace: user_project&.namespace, user: current_user) + Gitlab::Tracking.event( + 'API::Terraform::State', + 'terraform_state_api_request', + namespace: user_project&.namespace, + user: current_user, + project: user_project, + label: 'redis_hll_counters.terraform.p_terraform_state_api_unique_users_monthly', + context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, + event: 'p_terraform_state_api_unique_users').to_context] + ) end end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do @@ -49,7 +57,19 @@ module API end end - desc 'Get a terraform state by its name' + desc 'Get a Terraform state by its name' do + detail 'Get a Terraform state by its name' + success [ + { code: 200 }, + { code: 204, message: 'Empty state' } + ] + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Validation failure' } + ] + tags %w[terraform_state] + end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get do remote_state_handler.find_with_lock do |state| @@ -60,7 +80,18 @@ module API end end - desc 'Add a new terraform state or update an existing one' + desc 'Add a new Terraform state or update an existing one' do + detail 'Add a new Terraform state or update an existing one' + success [ + { code: 200 }, + { code: 204, message: 'No data provided' } + ] + failure [ + { code: 403, message: 'Forbidden' }, + { code: 422, message: 'Validation failure' } + ] + tags %w[terraform_state] + end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth post do authorize! :admin_terraform_state, user_project @@ -76,7 +107,16 @@ module API status :ok end - desc 'Delete a terraform state of a certain name' + desc 'Delete a Terraform state of a certain name' do + detail 'Delete a Terraform state of a certain name' + success code: 200 + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Validation failure' } + ] + tags %w[terraform_state] + end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth delete do authorize! :admin_terraform_state, user_project @@ -89,7 +129,17 @@ module API status :ok end - desc 'Lock a terraform state of a certain name' + desc 'Lock a Terraform state of a certain name' do + detail 'Lock a Terraform state of a certain name' + success code: 200 + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Conflict' }, + { code: 422, message: 'Validation failure' } + ] + tags %w[terraform_state] + end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth params do requires :ID, type: String, limit: 255, desc: 'Terraform state lock ID' @@ -128,7 +178,17 @@ module API end end - desc 'Unlock a terraform state of a certain name' + desc 'Unlock a Terraform state of a certain name' do + detail 'Unlock a Terraform state of a certain name' + success code: 200 + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Conflict' }, + { code: 422, message: 'Validation failure' } + ] + tags %w[terraform_state] + end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth params do optional :ID, type: String, limit: 255, desc: 'Terraform state lock ID' diff --git a/lib/api/terraform/state_version.rb b/lib/api/terraform/state_version.rb index ca37c786666..f98aeb5860e 100644 --- a/lib/api/terraform/state_version.rb +++ b/lib/api/terraform/state_version.rb @@ -14,7 +14,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do @@ -42,7 +42,15 @@ module API end end - desc 'Get a terraform state version' + desc 'Get a Terraform state version' do + detail 'Get a Terraform state version' + success File + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[terraform_state] + end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get do find_version(params[:serial]) do |version| @@ -52,7 +60,15 @@ module API end end - desc 'Delete a terraform state version' + desc 'Delete a Terraform state version' do + detail 'Delete a Terraform state version' + success code: 204 + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[terraform_state] + end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth delete do authorize! :admin_terraform_state, user_project diff --git a/lib/api/topics.rb b/lib/api/topics.rb index 38cfdc44021..b16b40244d4 100644 --- a/lib/api/topics.rb +++ b/lib/api/topics.rb @@ -11,7 +11,9 @@ module API success Entities::Projects::Topic end params do - optional :search, type: String, desc: 'Return list of topics matching the search criteria' + optional :search, type: String, + desc: 'Return list of topics matching the search criteria', + documentation: { example: 'search' } optional :without_projects, type: Boolean, desc: 'Return list of topics without assigned projects' use :pagination end @@ -42,7 +44,8 @@ module API requires :name, type: String, desc: 'Slug (name)' requires :title, type: String, desc: 'Title' optional :description, type: String, desc: 'Description' - optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for topic' + optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for topic', + documentation: { type: 'file' } end post 'topics' do authenticated_as_admin! @@ -65,7 +68,8 @@ module API optional :name, type: String, desc: 'Slug (name)' optional :title, type: String, desc: 'Title' optional :description, type: String, desc: 'Description' - optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for topic' + optional :avatar, type: ::API::Validations::Types::WorkhorseFile, desc: 'Avatar image for topic', + documentation: { type: 'file' } end put 'topics/:id' do authenticated_as_admin! diff --git a/lib/api/unleash.rb b/lib/api/unleash.rb index 1fbd7cf5afc..38ce4bd7f32 100644 --- a/lib/api/unleash.rb +++ b/lib/api/unleash.rb @@ -4,14 +4,16 @@ module API class Unleash < ::API::Base include PaginationParams + unleash_tags = %w[unleash_api] + feature_category :feature_flags namespace :feature_flags do resource :unleash, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do params do requires :project_id, type: String, desc: 'The ID of a project' - optional :instance_id, type: String, desc: 'The Instance ID of Unleash Client' - optional :app_name, type: String, desc: 'The Application Name of Unleash Client' + optional :instance_id, type: String, desc: 'The instance ID of Unleash Client' + optional :app_name, type: String, desc: 'The application name of Unleash Client' end route_param :project_id do before do @@ -23,26 +25,22 @@ module API status :ok end - desc 'Get a list of features (deprecated, v2 client support)' - get 'features' do - if ::Feature.enabled?(:cache_unleash_client_api, project) - present_feature_flags - else - present :version, 1 - present :features, feature_flags, with: ::API::Entities::UnleashFeature - end + desc 'Get a list of features (deprecated, v2 client support)' do + is_array true + tags unleash_tags + end + get 'features', urgency: :low do + present_feature_flags end # We decrease the urgency of this endpoint until the maxmemory issue of redis-cache has been resolved. # See https://gitlab.com/gitlab-org/gitlab/-/issues/365575#note_1033611872 for more information. - desc 'Get a list of features' + desc 'Get a list of features' do + is_array true + tags unleash_tags + end get 'client/features', urgency: :low do - if ::Feature.enabled?(:cache_unleash_client_api, project) - present_feature_flags - else - present :version, 1 - present :features, feature_flags, with: ::API::Entities::UnleashFeature - end + present_feature_flags end post 'client/register' do @@ -50,7 +48,7 @@ module API status :ok end - post 'client/metrics' do + post 'client/metrics', urgency: :low do # not supported yet status :ok end diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb index 388aa5e375c..e9420f1e2b7 100644 --- a/lib/api/user_counts.rb +++ b/lib/api/user_counts.rb @@ -8,17 +8,12 @@ module API resource :user_counts do desc 'Return the user specific counts' do detail 'Assigned open issues, assigned MRs and pending todos count' + success Entities::UserCounts end get do unauthorized! unless current_user - { - merge_requests: current_user.assigned_open_merge_requests_count, # @deprecated - assigned_issues: current_user.assigned_open_issues_count, - assigned_merge_requests: current_user.assigned_open_merge_requests_count, - review_requested_merge_requests: current_user.review_requested_open_merge_requests_count, - todos: current_user.todos_pending_count - } + present current_user, with: Entities::UserCounts end end end diff --git a/lib/api/users.rb b/lib/api/users.rb index 7f44e46f1ca..72c121bca03 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -8,9 +8,18 @@ module API allow_access_with_scope :read_user, if: -> (request) { request.get? || request.head? } - feature_category :users, ['/users/:id/custom_attributes', '/users/:id/custom_attributes/:key'] - - urgency :medium, ['/users/:id/custom_attributes', '/users/:id/custom_attributes/:key'] + feature_category :users, + %w[ + /users/:id/custom_attributes + /users/:id/custom_attributes/:key + /users/:id/associations_count + ] + + urgency :medium, + %w[ + /users/:id/custom_attributes + /users/:id/custom_attributes/:key + ] resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do include CustomAttributesEndpoints @@ -20,15 +29,9 @@ module API end helpers Helpers::UsersHelpers + helpers Gitlab::Tracking::Helpers::WeakPasswordErrorEvent helpers do - # rubocop: disable CodeReuse/ActiveRecord - def find_user_by_id(params) - id = params[:user_id] || params[:id] - User.find_by(id: id) || not_found!('User') - end - # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def reorder_users(users) if params[:order_by] && params[:sort] @@ -75,6 +78,31 @@ module API end end + resources ':id/associations_count' do + helpers do + def present_entity(result) + present result, + with: ::API::Entities::UserAssociationsCount + end + end + + desc "Returns a list of a specified user's count of projects, groups, issues and merge requests." + params do + requires :id, + type: Integer, + desc: 'ID of the user to query.' + end + get do + authenticate! + + user = find_user_by_id(params) + forbidden! unless can?(current_user, :get_user_associations_count, user) + not_found!('User') unless user + + present_entity(user) + end + end + desc 'Get the list of users' do success Entities::UserBasic end @@ -279,6 +307,8 @@ module API .by_username(user.username) .any? + track_weak_password_error(user, 'API::Users', 'create') + render_validation_error!(user) end end @@ -324,6 +354,7 @@ module API if result[:status] == :success present user, with: Entities::UserWithAdmin, current_user: current_user else + track_weak_password_error(user, 'API::Users', 'update') render_validation_error!(user) end end @@ -402,16 +433,16 @@ module API success Entities::SSHKey end params do - requires :id, type: Integer, desc: 'The ID of the user' + requires :user_id, type: Integer, desc: 'The ID of the user' requires :key, type: String, desc: 'The new SSH key' requires :title, type: String, desc: 'The title of the new SSH key' optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)' end # rubocop: disable CodeReuse/ActiveRecord - post ":id/keys", feature_category: :authentication_and_authorization do + post ":user_id/keys", feature_category: :authentication_and_authorization do authenticated_as_admin! - user = User.find_by(id: params.delete(:id)) + user = User.find_by(id: params.delete(:user_id)) not_found!('User') unless user key = ::Keys::CreateService.new(current_user, declared_params(include_missing: false).merge(user: user)).execute diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb index c86b7785ce2..e4a26838746 100644 --- a/lib/api/v3/github.rb +++ b/lib/api/v3/github.rb @@ -58,7 +58,7 @@ module API project = find_project!( ::Gitlab::Jira::Dvcs.restore_full_path(**params.slice(:namespace, :project).symbolize_keys) ) - not_found! unless can?(current_user, :download_code, project) + not_found! unless can?(current_user, :read_code, project) project end diff --git a/lib/api/validations/validators/email_or_email_list.rb b/lib/api/validations/validators/email_or_email_list.rb index da665f39130..715a29c613d 100644 --- a/lib/api/validations/validators/email_or_email_list.rb +++ b/lib/api/validations/validators/email_or_email_list.rb @@ -9,7 +9,12 @@ module API return unless value - return if value.split(',').map { |v| ValidateEmail.valid?(v) }.all? + case value + when String + return if value.split(',').map { |v| ValidateEmail.valid?(v) }.all? + when Array + return if value.map { |v| ValidateEmail.valid?(v) }.all? + end raise Grape::Exceptions::Validation.new( params: [@scope.full_name(attr_name)], diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index bb8ad5c4285..2058f5de706 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -28,6 +28,11 @@ module API desc 'Get a list of wiki pages' do success Entities::WikiPageBasic + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[wikis] + is_array true end params do optional :with_content, type: Boolean, default: false, desc: "Include pages' content" @@ -47,6 +52,10 @@ module API desc 'Get a wiki page' do success Entities::WikiPage + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[wikis] end params do requires :slug, type: String, desc: 'The slug of a wiki page' @@ -67,6 +76,12 @@ module API desc 'Create a wiki page' do success Entities::WikiPage + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[wikis] end params do requires :title, type: String, desc: 'Title of a wiki page' @@ -88,6 +103,12 @@ module API desc 'Update a wiki page' do success Entities::WikiPage + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[wikis] end params do optional :title, type: String, desc: 'Title of a wiki page' @@ -110,7 +131,14 @@ module API end end - desc 'Delete a wiki page' + desc 'Delete a wiki page' do + success code: 204 + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' } + ] + tags %w[wikis] + end params do requires :slug, type: String, desc: 'The slug of a wiki page' end @@ -131,6 +159,10 @@ module API desc 'Upload an attachment to the wiki repository' do detail 'This feature was introduced in GitLab 11.3.' success Entities::WikiAttachment + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[wikis] end params do requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded', documentation: { type: 'file' } -- cgit v1.2.1