diff --git a/changelogs/unreleased/zj-remove-ci-api-v1.yml b/changelogs/unreleased/zj-remove-ci-api-v1.yml new file mode 100644 index 00000000000..8f2dc321b36 --- /dev/null +++ b/changelogs/unreleased/zj-remove-ci-api-v1.yml @@ -0,0 +1,5 @@ +--- +title: Remove CI API v1 +merge_request: +author: +type: removed diff --git a/config/routes/ci.rb b/config/routes/ci.rb index 8d23aa8fbf6..cbd4c2db852 100644 --- a/config/routes/ci.rb +++ b/config/routes/ci.rb @@ -1,8 +1,4 @@ namespace :ci do - # CI API - Ci::API::API.logger Rails.logger - mount Ci::API::API => '/api' - resource :lint, only: [:show, :create] root to: redirect('/') diff --git a/doc/api/ci/README.md b/doc/api/ci/README.md deleted file mode 100644 index 96a281e27c8..00000000000 --- a/doc/api/ci/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# GitLab CI API - -## Purpose - -The main purpose of GitLab CI API is to provide the necessary data and context -for GitLab CI Runners. - -All relevant information about the consumer API can be found in a -[separate document](../../api/README.md). - -## API Prefix - -The current CI API prefix is `/ci/api/v1`. - -You need to prepend this prefix to all examples in this documentation, like: - -```bash -GET /ci/api/v1/builds/:id/artifacts -``` - -## Resources - -- [Builds](builds.md) -- [Runners](runners.md) diff --git a/doc/api/ci/builds.md b/doc/api/ci/builds.md deleted file mode 100644 index c8374d94716..00000000000 --- a/doc/api/ci/builds.md +++ /dev/null @@ -1,147 +0,0 @@ -# Builds API - -API used by runners to receive and update builds. - ->**Note:** -This API is intended to be used only by Runners as their own -communication channel. For the consumer API see the -[Jobs API](../jobs.md). - -## Authentication - -This API uses two types of authentication: - -1. Unique Runner's token which is the token assigned to the Runner after it - has been registered. - -2. Using the build authorization token. - This is project's CI token that can be found under the **Builds** section of - a project's settings. The build authorization token can be passed as a - parameter or a value of `BUILD-TOKEN` header. - -These two methods of authentication are interchangeable. - -## Builds - -### Runs oldest pending build by runner - -``` -POST /ci/api/v1/builds/register -``` - -| Attribute | Type | Required | Description | -|-----------|---------|----------|---------------------| -| `token` | string | yes | Unique runner token | - - -``` -curl --request POST "https://gitlab.example.com/ci/api/v1/builds/register" --form "token=t0k3n" -``` - -**Responses:** - -| Status | Data |Description | -|--------|------|---------------------------------------------------------------------------| -| `201` | yes | When a build is scheduled for a runner | -| `204` | no | When no builds are scheduled for a runner (for GitLab Runner >= `v1.3.0`) | -| `403` | no | When invalid token is used or no token is sent | -| `404` | no | When no builds are scheduled for a runner (for GitLab Runner < `v1.3.0`) **or** when the runner is set to `paused` in GitLab runner's configuration page | - -### Update details of an existing build - -``` -PUT /ci/api/v1/builds/:id -``` - -| Attribute | Type | Required | Description | -|-----------|---------|----------|----------------------| -| `id` | integer | yes | The ID of a project | -| `token` | string | yes | Unique runner token | -| `state` | string | no | The state of a build | -| `trace` | string | no | The trace of a build | - -``` -curl --request PUT "https://gitlab.example.com/ci/api/v1/builds/1234" --form "token=t0k3n" --form "state=running" --form "trace=Running git clone...\n" -``` - -### Incremental build trace update - -Using this method you need to send trace content as a request body. You also need to provide the `Content-Range` header -with a range of sent trace part. Note that you need to send parts in the proper order, so the begining of the part -must start just after the end of the previous part. If you provide the wrong part, then GitLab CI API will return `416 -Range Not Satisfiable` response with a header `Range: 0-X`, where `X` is the current trace length. - -For example, if you receive `Range: 0-11` in the response, then your next part must contain a `Content-Range: 11-...` -header and a trace part covered by this range. - -For a valid update API will return `202` response with: -* `Build-Status: {status}` header containing current status of the build, -* `Range: 0-{length}` header with the current trace length. - -``` -PATCH /ci/api/v1/builds/:id/trace.txt -``` - -Parameters: - -| Attribute | Type | Required | Description | -|-----------|---------|----------|----------------------| -| `id` | integer | yes | The ID of a build | - -Headers: - -| Attribute | Type | Required | Description | -|-----------------|---------|----------|-----------------------------------| -| `BUILD-TOKEN` | string | yes | The build authorization token | -| `Content-Range` | string | yes | Bytes range of trace that is sent | - -``` -curl --request PATCH "https://gitlab.example.com/ci/api/v1/builds/1234/trace.txt" --header "BUILD-TOKEN=build_t0k3n" --header "Content-Range=0-21" --data "Running git clone...\n" -``` - - -### Upload artifacts to build - -``` -POST /ci/api/v1/builds/:id/artifacts -``` - -| Attribute | Type | Required | Description | -|-----------|---------|----------|-------------------------------| -| `id` | integer | yes | The ID of a build | -| `token` | string | yes | The build authorization token | -| `file` | mixed | yes | Artifacts file | - -``` -curl --request POST "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" --form "token=build_t0k3n" --form "file=@/path/to/file" -``` - -### Download the artifacts file from build - -``` -GET /ci/api/v1/builds/:id/artifacts -``` - -| Attribute | Type | Required | Description | -|-----------|---------|----------|-------------------------------| -| `id` | integer | yes | The ID of a build | -| `token` | string | yes | The build authorization token | - -``` -curl "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" --form "token=build_t0k3n" -``` - -### Remove the artifacts file from build - -``` -DELETE /ci/api/v1/builds/:id/artifacts -``` - -| Attribute | Type | Required | Description | -|-----------|---------|----------|-------------------------------| -| ` id` | integer | yes | The ID of a build | -| `token` | string | yes | The build authorization token | - -``` -curl --request DELETE "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" --form "token=build_t0k3n" -``` diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md deleted file mode 100644 index e4a6dc809b1..00000000000 --- a/doc/api/ci/lint.md +++ /dev/null @@ -1,51 +0,0 @@ -# Validate the .gitlab-ci.yml (API) - -> [Introduced][ce-5953] in GitLab 8.12. - -Checks if your .gitlab-ci.yml file is valid. - -``` -POST ci/lint -``` - -| Attribute | Type | Required | Description | -| ---------- | ------- | -------- | -------- | -| `content` | string | yes | the .gitlab-ci.yaml content| - -```bash -curl --header "Content-Type: application/json" https://gitlab.example.com/api/v4/ci/lint --data '{"content": "{ \"image\": \"ruby:2.1\", \"services\": [\"postgres\"], \"before_script\": [\"gem install bundler\", \"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}' -``` - -Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very picky about indentation and spaces. - -Example responses: - -* Valid content: - - ```json - { - "status": "valid", - "errors": [] - } - ``` - -* Invalid content: - - ```json - { - "status": "invalid", - "errors": [ - "variables config should be a hash of key value pairs" - ] - } - ``` - -* Without the content attribute: - - ```json - { - "error": "content is missing" - } - ``` - -[ce-5953]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5953 diff --git a/doc/api/ci/runners.md b/doc/api/ci/runners.md deleted file mode 100644 index 342c039dad8..00000000000 --- a/doc/api/ci/runners.md +++ /dev/null @@ -1,59 +0,0 @@ -# Register and Delete Runners API - -API used by Runners to register and delete themselves. - ->**Note:** -This API is intended to be used only by Runners as their own -communication channel. For the consumer API see the -[new Runners API](../runners.md). - -## Authentication - -This API uses two types of authentication: - -1. Unique Runner's token, which is the token assigned to the Runner after it - has been registered. This token can be found on the Runner's edit page (go to - **Project > Runners**, select one of the Runners listed under **Runners activated for - this project**). - -2. Using Runners' registration token. - This is a token that can be found in project's settings. - It can also be found in the **Admin > Runners** settings area. - There are two types of tokens you can pass: shared Runner registration - token or project specific registration token. - -## Register a new runner - -Used to make GitLab CI aware of available runners. - -```sh -POST /ci/api/v1/runners/register -``` - -| Attribute | Type | Required | Description | -| --------- | ------- | --------- | ----------- | -| `token` | string | yes | Runner's registration token | - -Example request: - -```sh -curl --request POST "https://gitlab.example.com/ci/api/v1/runners/register" --form "token=t0k3n" -``` - -## Delete a Runner - -Used to remove a Runner. - -```sh -DELETE /ci/api/v1/runners/delete -``` - -| Attribute | Type | Required | Description | -| --------- | ------- | --------- | ----------- | -| `token` | string | yes | Unique Runner's token | - -Example request: - -```sh -curl --request DELETE "https://gitlab.example.com/ci/api/v1/runners/delete" --form "token=t0k3n" -``` diff --git a/doc/ci/api/README.md b/doc/ci/api/README.md deleted file mode 100644 index 98f37935427..00000000000 --- a/doc/ci/api/README.md +++ /dev/null @@ -1 +0,0 @@ -This document was moved to a [new location](../../api/ci/README.md). diff --git a/doc/ci/api/builds.md b/doc/ci/api/builds.md deleted file mode 100644 index 0563a367609..00000000000 --- a/doc/ci/api/builds.md +++ /dev/null @@ -1 +0,0 @@ -This document was moved to a [new location](../../api/ci/builds.md). diff --git a/doc/ci/api/runners.md b/doc/ci/api/runners.md deleted file mode 100644 index 1027363851c..00000000000 --- a/doc/ci/api/runners.md +++ /dev/null @@ -1 +0,0 @@ -This document was moved to a [new location](../../api/ci/runners.md). diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md index 0c0d482499a..fac91935a45 100644 --- a/doc/topics/authentication/index.md +++ b/doc/topics/authentication/index.md @@ -37,7 +37,6 @@ This page gathers all the resources for the topic **Authentication** within GitL - [Private Tokens](../../api/README.md#private-tokens) - [Impersonation tokens](../../api/README.md#impersonation-tokens) - [GitLab as an OAuth2 provider](../../api/oauth2.md#gitlab-as-an-oauth2-provider) -- [GitLab Runner API - Authentication](../../api/ci/runners.md#authentication) ## Third-party resources diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb deleted file mode 100644 index 24bb3649a76..00000000000 --- a/lib/ci/api/api.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Ci - module API - class API < Grape::API - include ::API::APIGuard - version 'v1', using: :path - - rescue_from ActiveRecord::RecordNotFound do - rack_response({ 'message' => '404 Not found' }.to_json, 404) - end - - # Retain 405 error rather than a 500 error for Grape 0.15.0+. - # https://github.com/ruby-grape/grape/blob/a3a28f5b5dfbb2797442e006dbffd750b27f2a76/UPGRADING.md#changes-to-method-not-allowed-routes - rescue_from Grape::Exceptions::MethodNotAllowed do |e| - error! e.message, e.status, e.headers - end - - rescue_from Grape::Exceptions::Base do |e| - error! e.message, e.status, e.headers - end - - rescue_from :all do |exception| - handle_api_exception(exception) - end - - content_type :txt, 'text/plain' - content_type :json, 'application/json' - format :json - - helpers ::SentryHelper - helpers ::Ci::API::Helpers - helpers ::API::Helpers - helpers Gitlab::CurrentSettings - - mount ::Ci::API::Builds - mount ::Ci::API::Runners - mount ::Ci::API::Triggers - end - end -end diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb deleted file mode 100644 index 79058c02ce5..00000000000 --- a/lib/ci/api/builds.rb +++ /dev/null @@ -1,219 +0,0 @@ -module Ci - module API - # Builds API - class Builds < Grape::API - resource :builds do - # Runs oldest pending build by runner - Runners only - # - # Parameters: - # token (required) - The uniq token of runner - # - # Example Request: - # POST /builds/register - post "register" do - authenticate_runner! - required_attributes! [:token] - not_found! unless current_runner.active? - update_runner_info - - if current_runner.is_runner_queue_value_latest?(params[:last_update]) - header 'X-GitLab-Last-Update', params[:last_update] - Gitlab::Metrics.add_event(:build_not_found_cached) - return build_not_found! - end - - new_update = current_runner.ensure_runner_queue_value - - result = Ci::RegisterJobService.new(current_runner).execute - - if result.valid? - if result.build - Gitlab::Metrics.add_event(:build_found, - project: result.build.project.full_path) - - present result.build, with: Entities::BuildDetails - else - Gitlab::Metrics.add_event(:build_not_found) - - header 'X-GitLab-Last-Update', new_update - - build_not_found! - end - else - # We received build that is invalid due to concurrency conflict - Gitlab::Metrics.add_event(:build_invalid) - conflict! - end - end - - # Update an existing build - Runners only - # - # Parameters: - # id (required) - The ID of a project - # state (optional) - The state of a build - # trace (optional) - The trace of a build - # Example Request: - # PUT /builds/:id - put ":id" do - authenticate_runner! - build = Ci::Build.where(runner_id: current_runner.id).running.find(params[:id]) - validate_build!(build) - - update_runner_info - - build.trace.set(params[:trace]) if params[:trace] - - Gitlab::Metrics.add_event(:update_build, - project: build.project.full_path) - - case params[:state].to_s - when 'success' - build.success - when 'failed' - build.drop - end - end - - # Send incremental log update - Runners only - # - # Parameters: - # id (required) - The ID of a build - # Body: - # content of logs to append - # Headers: - # Content-Range (required) - range of content that was sent - # BUILD-TOKEN (required) - The build authorization token - # Example Request: - # PATCH /builds/:id/trace.txt - patch ":id/trace.txt" do - build = authenticate_build! - - error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range') - content_range = request.headers['Content-Range'] - content_range = content_range.split('-') - - stream_size = build.trace.append(request.body.read, content_range[0].to_i) - if stream_size < 0 - return error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{-stream_size}" }) - end - - status 202 - header 'Build-Status', build.status - header 'Range', "0-#{stream_size}" - end - - # Authorize artifacts uploading for build - Runners only - # - # Parameters: - # id (required) - The ID of a build - # token (required) - The build authorization token - # filesize (optional) - the size of uploaded file - # Example Request: - # POST /builds/:id/artifacts/authorize - post ":id/artifacts/authorize" do - require_gitlab_workhorse! - Gitlab::Workhorse.verify_api_request!(headers) - not_allowed! unless Gitlab.config.artifacts.enabled - build = authenticate_build! - forbidden!('build is not running') unless build.running? - - if params[:filesize] - file_size = params[:filesize].to_i - file_to_large! unless file_size < max_artifacts_size - end - - status 200 - content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE - Gitlab::Workhorse.artifact_upload_ok - end - - # Upload artifacts to build - Runners only - # - # Parameters: - # id (required) - The ID of a build - # token (required) - The build authorization token - # file (required) - Artifacts file - # expire_in (optional) - Specify when artifacts should expire (ex. 7d) - # Parameters (accelerated by GitLab Workhorse): - # file.path - path to locally stored body (generated by Workhorse) - # file.name - real filename as send in Content-Disposition - # file.type - real content type as send in Content-Type - # metadata.path - path to locally stored body (generated by Workhorse) - # metadata.name - filename (generated by Workhorse) - # Headers: - # BUILD-TOKEN (required) - The build authorization token, the same as token - # Body: - # The file content - # - # Example Request: - # POST /builds/:id/artifacts - post ":id/artifacts" do - require_gitlab_workhorse! - not_allowed! unless Gitlab.config.artifacts.enabled - build = authenticate_build! - forbidden!('Build is not running!') unless build.running? - - artifacts_upload_path = ArtifactUploader.artifacts_upload_path - artifacts = uploaded_file(:file, artifacts_upload_path) - metadata = uploaded_file(:metadata, artifacts_upload_path) - - bad_request!('Missing artifacts file!') unless artifacts - file_to_large! unless artifacts.size < max_artifacts_size - - build.artifacts_file = artifacts - build.artifacts_metadata = metadata - build.artifacts_expire_in = - params['expire_in'] || - Gitlab::CurrentSettings.current_application_settings - .default_artifacts_expire_in - - if build.save - present(build, with: Entities::BuildDetails) - else - render_validation_error!(build) - end - end - - # Download the artifacts file from build - Runners only - # - # Parameters: - # id (required) - The ID of a build - # token (required) - The build authorization token - # Headers: - # BUILD-TOKEN (required) - The build authorization token, the same as token - # Example Request: - # GET /builds/:id/artifacts - get ":id/artifacts" do - build = authenticate_build! - artifacts_file = build.artifacts_file - - unless artifacts_file.exists? - not_found! - end - - unless artifacts_file.file_storage? - return redirect_to build.artifacts_file.url - end - - present_file!(artifacts_file.path, artifacts_file.filename) - end - - # Remove the artifacts file from build - Runners only - # - # Parameters: - # id (required) - The ID of a build - # token (required) - The build authorization token - # Headers: - # BUILD-TOKEN (required) - The build authorization token, the same as token - # Example Request: - # DELETE /builds/:id/artifacts - delete ":id/artifacts" do - build = authenticate_build! - - status(200) - build.erase_artifacts! - end - end - end - end -end diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb deleted file mode 100644 index 31f66dd5a58..00000000000 --- a/lib/ci/api/entities.rb +++ /dev/null @@ -1,93 +0,0 @@ -module Ci - module API - module Entities - class Commit < Grape::Entity - expose :id, :sha, :project_id, :created_at - expose :status, :finished_at, :duration - expose :git_commit_message, :git_author_name, :git_author_email - end - - class CommitWithBuilds < Commit - expose :builds - end - - class ArtifactFile < Grape::Entity - expose :filename, :size - end - - class BuildOptions < Grape::Entity - expose :image - expose :services - expose :artifacts - expose :cache - expose :dependencies - expose :after_script - end - - class Build < Grape::Entity - expose :id, :ref, :tag, :sha, :status - expose :name, :token, :stage - expose :project_id - expose :project_name - expose :artifacts_file, using: ArtifactFile, if: ->(build, _) { build.artifacts? } - end - - class BuildCredentials < Grape::Entity - expose :type, :url, :username, :password - end - - class BuildDetails < Build - expose :commands - expose :repo_url - expose :before_sha - expose :allow_git_fetch - expose :token - expose :artifacts_expire_at, if: ->(build, _) { build.artifacts? } - - expose :options do |model| - # This part ensures that output of old API is still the same after adding support - # for extended docker configuration options, used by new API - # - # I'm leaving this here, not in the model, because it should be removed at the same time - # when old API will be removed (planned for August 2017). - model.options.dup.tap do |options| - options[:image] = options[:image][:name] if options[:image].is_a?(Hash) - options[:services]&.map! do |service| - if service.is_a?(Hash) - service[:name] - else - service - end - end - end - end - - expose :timeout do |model| - model.timeout - end - - expose :variables - expose :depends_on_builds, using: Build - - expose :credentials, using: BuildCredentials - end - - class Runner < Grape::Entity - expose :id, :token - end - - class RunnerProject < Grape::Entity - expose :id, :project_id, :runner_id - end - - class WebHook < Grape::Entity - expose :id, :project_id, :url - end - - class TriggerRequest < Grape::Entity - expose :id, :variables - expose :pipeline, using: Commit, as: :commit - end - end - end -end diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb deleted file mode 100644 index a40b6ab6c9f..00000000000 --- a/lib/ci/api/helpers.rb +++ /dev/null @@ -1,89 +0,0 @@ -module Ci - module API - module Helpers - BUILD_TOKEN_HEADER = "HTTP_BUILD_TOKEN".freeze - BUILD_TOKEN_PARAM = :token - UPDATE_RUNNER_EVERY = 10 * 60 - - def authenticate_runners! - forbidden! unless runner_registration_token_valid? - end - - def authenticate_runner! - forbidden! unless current_runner - end - - def authenticate_build! - build = Ci::Build.find_by_id(params[:id]) - - validate_build!(build) do - forbidden! unless build_token_valid?(build) - end - - build - end - - def validate_build!(build) - not_found! unless build - - yield if block_given? - - project = build.project - forbidden!('Project has been deleted!') if project.nil? || project.pending_delete? - forbidden!('Build has been erased!') if build.erased? - end - - def runner_registration_token_valid? - ActiveSupport::SecurityUtils.variable_size_secure_compare( - params[:token], - current_application_settings.runners_registration_token) - end - - def build_token_valid?(build) - token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s - - # We require to also check `runners_token` to maintain compatibility with old version of runners - token && (build.valid_token?(token) || build.project.valid_runners_token?(token)) - end - - def update_runner_info - return unless update_runner? - - current_runner.contacted_at = Time.now - current_runner.assign_attributes(get_runner_version_from_params) - current_runner.save if current_runner.changed? - end - - def update_runner? - # Use a random threshold to prevent beating DB updates. - # It generates a distribution between [40m, 80m]. - # - contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY) - - current_runner.contacted_at.nil? || - (Time.now - current_runner.contacted_at) >= contacted_at_max_age - end - - def build_not_found! - if headers['User-Agent'].to_s =~ /gitlab-ci-multi-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? / - no_content! - else - not_found! - end - end - - def current_runner - @runner ||= Runner.find_by_token(params[:token].to_s) - end - - def get_runner_version_from_params - return unless params["info"].present? - attributes_for_keys(%w(name version revision platform architecture), params["info"]) - end - - def max_artifacts_size - current_application_settings.max_artifacts_size.megabytes.to_i - end - end - end -end diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb deleted file mode 100644 index 45aa2adccf5..00000000000 --- a/lib/ci/api/runners.rb +++ /dev/null @@ -1,50 +0,0 @@ -module Ci - module API - class Runners < Grape::API - resource :runners do - desc 'Delete a runner' - params do - requires :token, type: String, desc: 'The unique token of the runner' - end - delete "delete" do - authenticate_runner! - - status(200) - Ci::Runner.find_by_token(params[:token]).destroy - end - - desc 'Register a new runner' do - success Entities::Runner - end - params do - requires :token, type: String, desc: 'The unique token of the runner' - optional :description, type: String, desc: 'The description of the runner' - optional :tag_list, type: Array[String], desc: 'A list of tags the runner should run for' - optional :run_untagged, type: Boolean, desc: 'Flag if the runner should execute untagged jobs' - optional :locked, type: Boolean, desc: 'Lock this runner for this specific project' - end - post "register" do - runner_params = declared(params, include_missing: false).except(:token) - - runner = - if runner_registration_token_valid? - # Create shared runner. Requires admin access - Ci::Runner.create(runner_params.merge(is_shared: true)) - elsif project = Project.find_by(runners_token: params[:token]) - # Create a specific runner for project. - project.runners.create(runner_params) - end - - return forbidden! unless runner - - if runner.id - runner.update(get_runner_version_from_params) - present runner, with: Entities::Runner - else - not_found! - end - end - end - end - end -end diff --git a/lib/ci/api/triggers.rb b/lib/ci/api/triggers.rb deleted file mode 100644 index 6225203f223..00000000000 --- a/lib/ci/api/triggers.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Ci - module API - class Triggers < Grape::API - resource :projects do - desc 'Trigger a GitLab CI project build' do - success Entities::TriggerRequest - end - params do - requires :id, type: Integer, desc: 'The ID of a CI project' - requires :ref, type: String, desc: "The name of project's branch or tag" - requires :token, type: String, desc: 'The unique token of the trigger' - optional :variables, type: Hash, desc: 'Optional build variables' - end - post ":id/refs/:ref/trigger" do - project = Project.find_by(ci_id: params[:id]) - trigger = Ci::Trigger.find_by_token(params[:token]) - not_found! unless project && trigger - unauthorized! unless trigger.project == project - - # Validate variables - variables = params[:variables].to_h - unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) } - render_api_error!('variables needs to be a map of key-valued strings', 400) - end - - # create request and trigger builds - result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref], variables) - pipeline = result.pipeline - - if pipeline.persisted? - present result.trigger_request, with: Entities::TriggerRequest - else - render_validation_error!(pipeline) - end - end - end - end - end -end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb deleted file mode 100644 index 7ccba4ba3ec..00000000000 --- a/spec/requests/ci/api/builds_spec.rb +++ /dev/null @@ -1,912 +0,0 @@ -require 'spec_helper' - -describe Ci::API::Builds do - let(:runner) { FactoryGirl.create(:ci_runner, tag_list: %w(mysql ruby)) } - let(:project) { FactoryGirl.create(:project, shared_runners_enabled: false) } - let(:last_update) { nil } - - describe "Builds API for runners" do - let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') } - - before do - project.runners << runner - end - - describe "POST /builds/register" do - let!(:build) { create(:ci_build, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } - let(:user_agent) { 'gitlab-ci-multi-runner 1.5.2 (1-5-stable; go1.6.3; linux/amd64)' } - let!(:last_update) { } - let!(:new_update) { } - - before do - stub_container_registry_config(enabled: false) - end - - shared_examples 'no builds available' do - context 'when runner sends version in User-Agent' do - context 'for stable version' do - it 'gives 204 and set X-GitLab-Last-Update' do - expect(response).to have_http_status(204) - expect(response.header).to have_key('X-GitLab-Last-Update') - end - end - - context 'when last_update is up-to-date' do - let(:last_update) { runner.ensure_runner_queue_value } - - it 'gives 204 and set the same X-GitLab-Last-Update' do - expect(response).to have_http_status(204) - expect(response.header['X-GitLab-Last-Update']) - .to eq(last_update) - end - end - - context 'when last_update is outdated' do - let(:last_update) { runner.ensure_runner_queue_value } - let(:new_update) { runner.tick_runner_queue } - - it 'gives 204 and set a new X-GitLab-Last-Update' do - expect(response).to have_http_status(204) - expect(response.header['X-GitLab-Last-Update']) - .to eq(new_update) - end - end - - context 'for beta version' do - let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (1-5-stable; go1.6.3; linux/amd64)' } - it { expect(response).to have_http_status(204) } - end - end - - context "when runner doesn't send version in User-Agent" do - let(:user_agent) { 'Go-http-client/1.1' } - it { expect(response).to have_http_status(404) } - end - - context "when runner doesn't have a User-Agent" do - let(:user_agent) { nil } - it { expect(response).to have_http_status(404) } - end - end - - context 'when an old image syntax is used' do - before do - build.update!(options: { image: 'codeclimate' }) - end - - it 'starts a build' do - register_builds info: { platform: :darwin } - - expect(response).to have_http_status(201) - expect(json_response["options"]).to eq({ "image" => "codeclimate" }) - end - end - - context 'when a new image syntax is used' do - before do - build.update!(options: { image: { name: 'codeclimate' } }) - end - - it 'starts a build' do - register_builds info: { platform: :darwin } - - expect(response).to have_http_status(201) - expect(json_response["options"]).to eq({ "image" => "codeclimate" }) - end - end - - context 'when an old service syntax is used' do - before do - build.update!(options: { services: ['mysql'] }) - end - - it 'starts a build' do - register_builds info: { platform: :darwin } - - expect(response).to have_http_status(201) - expect(json_response["options"]).to eq({ "services" => ["mysql"] }) - end - end - - context 'when a new service syntax is used' do - before do - build.update!(options: { services: [name: 'mysql'] }) - end - - it 'starts a build' do - register_builds info: { platform: :darwin } - - expect(response).to have_http_status(201) - expect(json_response["options"]).to eq({ "services" => ["mysql"] }) - end - end - - context 'when no image or service is defined' do - before do - build.update!(options: {}) - end - - it 'starts a build' do - register_builds info: { platform: :darwin } - - expect(response).to have_http_status(201) - - expect(json_response["options"]).to be_empty - end - end - - context 'when there is a pending build' do - it 'starts a build' do - register_builds info: { platform: :darwin } - - expect(response).to have_http_status(201) - expect(response.headers).not_to have_key('X-GitLab-Last-Update') - expect(json_response['sha']).to eq(build.sha) - expect(runner.reload.platform).to eq("darwin") - expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] }) - expect(json_response["variables"]).to include( - { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true }, - { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true }, - { "key" => "DB_NAME", "value" => "postgres", "public" => true } - ) - end - - it 'updates runner info' do - expect { register_builds }.to change { runner.reload.contacted_at } - end - - context 'when concurrently updating build' do - before do - expect_any_instance_of(Ci::Build).to receive(:run!) - .and_raise(ActiveRecord::StaleObjectError.new(nil, nil)) - end - - it 'returns a conflict' do - register_builds info: { platform: :darwin } - - expect(response).to have_http_status(409) - expect(response.headers).not_to have_key('X-GitLab-Last-Update') - end - end - - context 'registry credentials' do - let(:registry_credentials) do - { 'type' => 'registry', - 'url' => 'registry.example.com:5005', - 'username' => 'gitlab-ci-token', - 'password' => build.token } - end - - context 'when registry is enabled' do - before do - stub_container_registry_config(enabled: true, host_port: 'registry.example.com:5005') - end - - it 'sends registry credentials key' do - register_builds info: { platform: :darwin } - - expect(json_response).to have_key('credentials') - expect(json_response['credentials']).to include(registry_credentials) - end - end - - context 'when registry is disabled' do - before do - stub_container_registry_config(enabled: false, host_port: 'registry.example.com:5005') - end - - it 'does not send registry credentials' do - register_builds info: { platform: :darwin } - - expect(json_response).to have_key('credentials') - expect(json_response['credentials']).not_to include(registry_credentials) - end - end - end - - context 'when docker configuration options are used' do - let!(:build) { create(:ci_build, :extended_options, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } - - it 'starts a build' do - register_builds info: { platform: :darwin } - - expect(response).to have_http_status(201) - expect(json_response['options']['image']).to eq('ruby:2.1') - expect(json_response['options']['services']).to eq(['postgres', 'docker:dind']) - end - end - end - - context 'when builds are finished' do - before do - build.success - register_builds - end - - it_behaves_like 'no builds available' - end - - context 'for other project with builds' do - before do - build.success - create(:ci_build, :pending) - register_builds - end - - it_behaves_like 'no builds available' - end - - context 'for shared runner' do - let!(:runner) { create(:ci_runner, :shared, token: "SharedRunner") } - - before do - register_builds(runner.token) - end - - it_behaves_like 'no builds available' - end - - context 'for triggered build' do - before do - trigger = create(:ci_trigger, project: project) - create(:ci_trigger_request_with_variables, pipeline: pipeline, builds: [build], trigger: trigger) - project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value") - end - - it "returns variables for triggers" do - register_builds info: { platform: :darwin } - - expect(response).to have_http_status(201) - expect(json_response["variables"]).to include( - { "key" => "CI_JOB_NAME", "value" => "spinach", "public" => true }, - { "key" => "CI_JOB_STAGE", "value" => "test", "public" => true }, - { "key" => "CI_PIPELINE_TRIGGERED", "value" => "true", "public" => true }, - { "key" => "DB_NAME", "value" => "postgres", "public" => true }, - { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }, - { "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false } - ) - end - end - - context 'with multiple builds' do - before do - build.success - end - - let!(:test_build) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) } - - it "returns dependent builds" do - register_builds info: { platform: :darwin } - - expect(response).to have_http_status(201) - expect(json_response["id"]).to eq(test_build.id) - expect(json_response["depends_on_builds"].count).to eq(1) - expect(json_response["depends_on_builds"][0]).to include('id' => build.id, 'name' => 'spinach') - end - end - - %w(name version revision platform architecture).each do |param| - context "updates runner #{param}" do - let(:value) { "#{param}_value" } - - subject { runner.read_attribute(param.to_sym) } - - it do - register_builds info: { param => value } - - expect(response).to have_http_status(201) - runner.reload - is_expected.to eq(value) - end - end - end - - context 'when build has no tags' do - before do - build.update(tags: []) - end - - context 'when runner is allowed to pick untagged builds' do - before do - runner.update_column(:run_untagged, true) - end - - it 'picks build' do - register_builds - - expect(response).to have_http_status 201 - end - end - - context 'when runner is not allowed to pick untagged builds' do - before do - runner.update_column(:run_untagged, false) - register_builds - end - - it_behaves_like 'no builds available' - end - end - - context 'when runner is paused' do - let(:runner) { create(:ci_runner, :inactive, token: 'InactiveRunner') } - - it 'responds with 404' do - register_builds - - expect(response).to have_http_status 404 - end - - it 'does not update runner info' do - expect { register_builds } - .not_to change { runner.reload.contacted_at } - end - end - - def register_builds(token = runner.token, **params) - new_params = params.merge(token: token, last_update: last_update) - - post ci_api("/builds/register"), new_params, { 'User-Agent' => user_agent } - end - end - - describe "PUT /builds/:id" do - let(:build) { create(:ci_build, :pending, :trace, pipeline: pipeline, runner_id: runner.id) } - - before do - build.run! - put ci_api("/builds/#{build.id}"), token: runner.token - end - - it "updates a running build" do - expect(response).to have_http_status(200) - end - - it 'does not override trace information when no trace is given' do - expect(build.reload.trace.raw).to eq 'BUILD TRACE' - end - - context 'job has been erased' do - let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) } - - it 'responds with forbidden' do - expect(response.status).to eq 403 - end - end - end - - describe 'PATCH /builds/:id/trace.txt' do - let(:build) do - attributes = { runner_id: runner.id, pipeline: pipeline } - create(:ci_build, :running, :trace, attributes) - end - - let(:headers) { { Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token, 'Content-Type' => 'text/plain' } } - let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) } - let(:update_interval) { 10.seconds.to_i } - - def patch_the_trace(content = ' appended', request_headers = nil) - unless request_headers - build.trace.read do |stream| - offset = stream.size - limit = offset + content.length - 1 - request_headers = headers.merge({ 'Content-Range' => "#{offset}-#{limit}" }) - end - end - - Timecop.travel(build.updated_at + update_interval) do - patch ci_api("/builds/#{build.id}/trace.txt"), content, request_headers - build.reload - end - end - - def initial_patch_the_trace - patch_the_trace(' appended', headers_with_range) - end - - def force_patch_the_trace - 2.times { patch_the_trace('') } - end - - before do - initial_patch_the_trace - end - - context 'when request is valid' do - it 'gets correct response' do - expect(response.status).to eq 202 - expect(build.reload.trace.raw).to eq 'BUILD TRACE appended' - expect(response.header).to have_key 'Range' - expect(response.header).to have_key 'Build-Status' - end - - context 'when build has been updated recently' do - it { expect { patch_the_trace }.not_to change { build.updated_at }} - - it 'changes the build trace' do - patch_the_trace - - expect(build.reload.trace.raw).to eq 'BUILD TRACE appended appended' - end - - context 'when Runner makes a force-patch' do - it { expect { force_patch_the_trace }.not_to change { build.updated_at }} - - it "doesn't change the build.trace" do - force_patch_the_trace - - expect(build.reload.trace.raw).to eq 'BUILD TRACE appended' - end - end - end - - context 'when build was not updated recently' do - let(:update_interval) { 15.minutes.to_i } - - it { expect { patch_the_trace }.to change { build.updated_at } } - - it 'changes the build.trace' do - patch_the_trace - - expect(build.reload.trace.raw).to eq 'BUILD TRACE appended appended' - end - - context 'when Runner makes a force-patch' do - it { expect { force_patch_the_trace }.to change { build.updated_at } } - - it "doesn't change the build.trace" do - force_patch_the_trace - - expect(build.reload.trace.raw).to eq 'BUILD TRACE appended' - end - end - end - - context 'when project for the build has been deleted' do - let(:build) do - attributes = { runner_id: runner.id, pipeline: pipeline } - create(:ci_build, :running, :trace, attributes) do |build| - build.project.update(pending_delete: true) - end - end - - it 'responds with forbidden' do - expect(response.status).to eq(403) - end - end - end - - context 'when Runner makes a force-patch' do - before do - force_patch_the_trace - end - - it 'gets correct response' do - expect(response.status).to eq 202 - expect(build.reload.trace.raw).to eq 'BUILD TRACE appended' - expect(response.header).to have_key 'Range' - expect(response.header).to have_key 'Build-Status' - end - end - - context 'when content-range start is too big' do - let(:headers_with_range) { headers.merge({ 'Content-Range' => '15-20' }) } - - it 'gets 416 error response with range headers' do - expect(response.status).to eq 416 - expect(response.header).to have_key 'Range' - expect(response.header['Range']).to eq '0-11' - end - end - - context 'when content-range start is too small' do - let(:headers_with_range) { headers.merge({ 'Content-Range' => '8-20' }) } - - it 'gets 416 error response with range headers' do - expect(response.status).to eq 416 - expect(response.header).to have_key 'Range' - expect(response.header['Range']).to eq '0-11' - end - end - - context 'when Content-Range header is missing' do - let(:headers_with_range) { headers } - - it { expect(response.status).to eq 400 } - end - - context 'when build has been errased' do - let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) } - - it { expect(response.status).to eq 403 } - end - end - - context "Artifacts" do - let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } - let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') } - let(:build) { create(:ci_build, :pending, pipeline: pipeline, runner_id: runner.id) } - let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") } - let(:post_url) { ci_api("/builds/#{build.id}/artifacts") } - let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") } - let(:get_url) { ci_api("/builds/#{build.id}/artifacts") } - let(:jwt_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } - let(:headers) { { "GitLab-Workhorse" => "1.0", Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt_token } } - let(:token) { build.token } - let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => token) } - - before do - build.run! - end - - describe "POST /builds/:id/artifacts/authorize" do - context "authorizes posting artifact to running build" do - it "using token as parameter" do - post authorize_url, { token: build.token }, headers - - expect(response).to have_http_status(200) - expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) - expect(json_response["TempPath"]).not_to be_nil - end - - it "using token as header" do - post authorize_url, {}, headers_with_token - - expect(response).to have_http_status(200) - expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) - expect(json_response["TempPath"]).not_to be_nil - end - - it "using runners token" do - post authorize_url, { token: build.project.runners_token }, headers - - expect(response).to have_http_status(200) - expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) - expect(json_response["TempPath"]).not_to be_nil - end - - it "reject requests that did not go through gitlab-workhorse" do - headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) - - post authorize_url, { token: build.token }, headers - - expect(response).to have_http_status(500) - end - end - - context "fails to post too large artifact" do - it "using token as parameter" do - stub_application_setting(max_artifacts_size: 0) - - post authorize_url, { token: build.token, filesize: 100 }, headers - - expect(response).to have_http_status(413) - end - - it "using token as header" do - stub_application_setting(max_artifacts_size: 0) - - post authorize_url, { filesize: 100 }, headers_with_token - - expect(response).to have_http_status(413) - end - end - - context 'authorization token is invalid' do - before do - post authorize_url, { token: 'invalid', filesize: 100 } - end - - it 'responds with forbidden' do - expect(response).to have_http_status(403) - end - end - end - - describe "POST /builds/:id/artifacts" do - context "disable sanitizer" do - before do - # by configuring this path we allow to pass temp file from any path - allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/') - end - - describe 'build has been erased' do - let(:build) { create(:ci_build, erased_at: Time.now) } - - before do - upload_artifacts(file_upload, headers_with_token) - end - - it 'responds with forbidden' do - expect(response.status).to eq 403 - end - end - - describe 'uploading artifacts for a running build' do - shared_examples 'successful artifacts upload' do - it 'updates successfully' do - response_filename = - json_response['artifacts_file']['filename'] - - expect(response).to have_http_status(201) - expect(response_filename).to eq(file_upload.original_filename) - end - end - - context 'uses regular file post' do - before do - upload_artifacts(file_upload, headers_with_token, false) - end - - it_behaves_like 'successful artifacts upload' - end - - context 'uses accelerated file post' do - before do - upload_artifacts(file_upload, headers_with_token, true) - end - - it_behaves_like 'successful artifacts upload' - end - - context 'updates artifact' do - before do - upload_artifacts(file_upload2, headers_with_token) - upload_artifacts(file_upload, headers_with_token) - end - - it_behaves_like 'successful artifacts upload' - end - - context 'when using runners token' do - let(:token) { build.project.runners_token } - - before do - upload_artifacts(file_upload, headers_with_token) - end - - it_behaves_like 'successful artifacts upload' - end - end - - context 'posts artifacts file and metadata file' do - let!(:artifacts) { file_upload } - let!(:metadata) { file_upload2 } - - let(:stored_artifacts_file) { build.reload.artifacts_file.file } - let(:stored_metadata_file) { build.reload.artifacts_metadata.file } - let(:stored_artifacts_size) { build.reload.artifacts_size } - - before do - post(post_url, post_data, headers_with_token) - end - - context 'posts data accelerated by workhorse is correct' do - let(:post_data) do - { 'file.path' => artifacts.path, - 'file.name' => artifacts.original_filename, - 'metadata.path' => metadata.path, - 'metadata.name' => metadata.original_filename } - end - - it 'stores artifacts and artifacts metadata' do - expect(response).to have_http_status(201) - expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename) - expect(stored_metadata_file.original_filename).to eq(metadata.original_filename) - expect(stored_artifacts_size).to eq(71759) - end - end - - context 'no artifacts file in post data' do - let(:post_data) do - { 'metadata' => metadata } - end - - it 'is expected to respond with bad request' do - expect(response).to have_http_status(400) - end - - it 'does not store metadata' do - expect(stored_metadata_file).to be_nil - end - end - end - - context 'with an expire date' do - let!(:artifacts) { file_upload } - let(:default_artifacts_expire_in) {} - - let(:post_data) do - { 'file.path' => artifacts.path, - 'file.name' => artifacts.original_filename, - 'expire_in' => expire_in } - end - - before do - stub_application_setting( - default_artifacts_expire_in: default_artifacts_expire_in) - - post(post_url, post_data, headers_with_token) - end - - context 'with an expire_in given' do - let(:expire_in) { '7 days' } - - it 'updates when specified' do - build.reload - expect(response).to have_http_status(201) - expect(json_response['artifacts_expire_at']).not_to be_empty - expect(build.artifacts_expire_at) - .to be_within(5.minutes).of(7.days.from_now) - end - end - - context 'with no expire_in given' do - let(:expire_in) { nil } - - it 'ignores if not specified' do - build.reload - expect(response).to have_http_status(201) - expect(json_response['artifacts_expire_at']).to be_nil - expect(build.artifacts_expire_at).to be_nil - end - - context 'with application default' do - context 'default to 5 days' do - let(:default_artifacts_expire_in) { '5 days' } - - it 'sets to application default' do - build.reload - expect(response).to have_http_status(201) - expect(json_response['artifacts_expire_at']) - .not_to be_empty - expect(build.artifacts_expire_at) - .to be_within(5.minutes).of(5.days.from_now) - end - end - - context 'default to 0' do - let(:default_artifacts_expire_in) { '0' } - - it 'does not set expire_in' do - build.reload - expect(response).to have_http_status(201) - expect(json_response['artifacts_expire_at']).to be_nil - expect(build.artifacts_expire_at).to be_nil - end - end - end - end - end - - context "artifacts file is too large" do - it "fails to post too large artifact" do - stub_application_setting(max_artifacts_size: 0) - upload_artifacts(file_upload, headers_with_token) - expect(response).to have_http_status(413) - end - end - - context "artifacts post request does not contain file" do - it "fails to post artifacts without file" do - post post_url, {}, headers_with_token - expect(response).to have_http_status(400) - end - end - - context 'GitLab Workhorse is not configured' do - it "fails to post artifacts without GitLab-Workhorse" do - post post_url, { token: build.token }, {} - expect(response).to have_http_status(403) - end - end - end - - context "artifacts are being stored outside of tmp path" do - before do - # by configuring this path we allow to pass file from @tmpdir only - # but all temporary files are stored in system tmp directory - @tmpdir = Dir.mktmpdir - allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir) - end - - after do - FileUtils.remove_entry @tmpdir - end - - it "fails to post artifacts for outside of tmp path" do - upload_artifacts(file_upload, headers_with_token) - expect(response).to have_http_status(400) - end - end - - def upload_artifacts(file, headers = {}, accelerated = true) - if accelerated - post post_url, { - 'file.path' => file.path, - 'file.name' => file.original_filename - }, headers - else - post post_url, { file: file }, headers - end - end - end - - describe 'DELETE /builds/:id/artifacts' do - let(:build) { create(:ci_build, :artifacts) } - - before do - delete delete_url, token: build.token - end - - shared_examples 'having removable artifacts' do - it 'removes build artifacts' do - build.reload - - expect(response).to have_http_status(200) - expect(build.artifacts_file.exists?).to be_falsy - expect(build.artifacts_metadata.exists?).to be_falsy - expect(build.artifacts_size).to be_nil - end - end - - context 'when using build token' do - before do - delete delete_url, token: build.token - end - - it_behaves_like 'having removable artifacts' - end - - context 'when using runnners token' do - before do - delete delete_url, token: build.project.runners_token - end - - it_behaves_like 'having removable artifacts' - end - end - - describe 'GET /builds/:id/artifacts' do - before do - get get_url, token: token - end - - context 'build has artifacts' do - let(:build) { create(:ci_build, :artifacts) } - let(:download_headers) do - { 'Content-Transfer-Encoding' => 'binary', - 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' } - end - - shared_examples 'having downloadable artifacts' do - it 'download artifacts' do - expect(response).to have_http_status(200) - expect(response.headers).to include download_headers - end - end - - context 'when using build token' do - let(:token) { build.token } - - it_behaves_like 'having downloadable artifacts' - end - - context 'when using runnners token' do - let(:token) { build.project.runners_token } - - it_behaves_like 'having downloadable artifacts' - end - end - - context 'build does not has artifacts' do - let(:token) { build.token } - - it 'responds with not found' do - expect(response).to have_http_status(404) - end - end - end - end - end -end diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb deleted file mode 100644 index 75059dd20a0..00000000000 --- a/spec/requests/ci/api/runners_spec.rb +++ /dev/null @@ -1,127 +0,0 @@ -require 'spec_helper' - -describe Ci::API::Runners do - include StubGitlabCalls - - let(:registration_token) { 'abcdefg123456' } - - before do - stub_gitlab_calls - stub_application_setting(runners_registration_token: registration_token) - end - - describe "POST /runners/register" do - context 'when runner token is provided' do - before do - post ci_api("/runners/register"), token: registration_token - end - - it 'creates runner with default values' do - expect(response).to have_http_status 201 - expect(Ci::Runner.first.run_untagged).to be true - expect(Ci::Runner.first.token).not_to eq(registration_token) - end - end - - context 'when runner description is provided' do - before do - post ci_api("/runners/register"), token: registration_token, - description: "server.hostname" - end - - it 'creates runner' do - expect(response).to have_http_status 201 - expect(Ci::Runner.first.description).to eq("server.hostname") - end - end - - context 'when runner tags are provided' do - before do - post ci_api("/runners/register"), token: registration_token, - tag_list: "tag1, tag2" - end - - it 'creates runner' do - expect(response).to have_http_status 201 - expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2)) - end - end - - context 'when option for running untagged jobs is provided' do - context 'when tags are provided' do - it 'creates runner' do - post ci_api("/runners/register"), token: registration_token, - run_untagged: false, - tag_list: ['tag'] - - expect(response).to have_http_status 201 - expect(Ci::Runner.first.run_untagged).to be false - end - end - - context 'when tags are not provided' do - it 'does not create runner' do - post ci_api("/runners/register"), token: registration_token, - run_untagged: false - - expect(response).to have_http_status 404 - end - end - end - - context 'when project token is provided' do - let(:project) { FactoryGirl.create(:project) } - - before do - post ci_api("/runners/register"), token: project.runners_token - end - - it 'creates runner' do - expect(response).to have_http_status 201 - expect(project.runners.size).to eq(1) - expect(Ci::Runner.first.token).not_to eq(registration_token) - expect(Ci::Runner.first.token).not_to eq(project.runners_token) - end - end - - context 'when token is invalid' do - it 'returns 403 error' do - post ci_api("/runners/register"), token: 'invalid' - - expect(response).to have_http_status 403 - end - end - - context 'when no token provided' do - it 'returns 400 error' do - post ci_api("/runners/register") - - expect(response).to have_http_status 400 - end - end - - %w(name version revision platform architecture).each do |param| - context "creates runner with #{param} saved" do - let(:value) { "#{param}_value" } - - subject { Ci::Runner.first.read_attribute(param.to_sym) } - - it do - post ci_api("/runners/register"), token: registration_token, info: { param => value } - expect(response).to have_http_status 201 - is_expected.to eq(value) - end - end - end - end - - describe "DELETE /runners/delete" do - it 'returns 200' do - runner = FactoryGirl.create(:ci_runner) - delete ci_api("/runners/delete"), token: runner.token - - expect(response).to have_http_status 200 - expect(Ci::Runner.count).to eq(0) - end - end -end diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb deleted file mode 100644 index 7c77ebb69a2..00000000000 --- a/spec/requests/ci/api/triggers_spec.rb +++ /dev/null @@ -1,90 +0,0 @@ -require 'spec_helper' - -describe Ci::API::Triggers do - describe 'POST /projects/:project_id/refs/:ref/trigger' do - let!(:trigger_token) { 'secure token' } - let!(:project) { create(:project, :repository, ci_id: 10) } - let!(:project2) { create(:project, ci_id: 11) } - - let!(:trigger) do - create(:ci_trigger, - project: project, - token: trigger_token, - owner: create(:user)) - end - - let(:options) do - { - token: trigger_token - } - end - - before do - stub_ci_pipeline_to_return_yaml_file - - project.add_developer(trigger.owner) - end - - context 'Handles errors' do - it 'returns bad request if token is missing' do - post ci_api("/projects/#{project.ci_id}/refs/master/trigger") - expect(response).to have_http_status(400) - end - - it 'returns not found if project is not found' do - post ci_api('/projects/0/refs/master/trigger'), options - expect(response).to have_http_status(404) - end - - it 'returns unauthorized if token is for different project' do - post ci_api("/projects/#{project2.ci_id}/refs/master/trigger"), options - expect(response).to have_http_status(401) - end - end - - context 'Have a commit' do - let(:pipeline) { project.pipelines.last } - - it 'creates builds' do - post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options - expect(response).to have_http_status(201) - pipeline.builds.reload - expect(pipeline.builds.pending.size).to eq(2) - expect(pipeline.builds.size).to eq(5) - end - - it 'returns bad request with no builds created if there\'s no commit for that ref' do - post ci_api("/projects/#{project.ci_id}/refs/other-branch/trigger"), options - expect(response).to have_http_status(400) - expect(json_response['message']['base']) - .to contain_exactly('Reference not found') - end - - context 'Validates variables' do - let(:variables) do - { 'TRIGGER_KEY' => 'TRIGGER_VALUE' } - end - - it 'validates variables to be a hash' do - post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: 'value') - expect(response).to have_http_status(400) - - expect(json_response['error']).to eq('variables is invalid') - end - - it 'validates variables needs to be a map of key-valued strings' do - post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: { key: %w(1 2) }) - expect(response).to have_http_status(400) - expect(json_response['message']).to eq('variables needs to be a map of key-valued strings') - end - - it 'creates trigger request with variables' do - post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: variables) - expect(response).to have_http_status(201) - pipeline.builds.reload - expect(pipeline.builds.first.trigger_request.variables).to eq(variables) - end - end - end - end -end