Read about our upcoming Code of Conduct on this issue

commit_statuses.rb 4.89 KB
Newer Older
1
2
# frozen_string_literal: true

Kamil Trzcinski's avatar
Kamil Trzcinski committed
3
4
5
require 'mime/types'

module API
6
  class CommitStatuses < Grape::API
7
8
9
    params do
      requires :id, type: String, desc: 'The ID of a project'
    end
10
    resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
11
12
      include PaginationParams

Kamil Trzcinski's avatar
Kamil Trzcinski committed
13
14
      before { authenticate! }

15
16
17
18
19
20
21
22
23
      desc "Get a commit's statuses" do
        success Entities::CommitStatus
      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'
24
        use :pagination
25
      end
26
      # rubocop: disable CodeReuse/ActiveRecord
Kamil Trzcinski's avatar
Kamil Trzcinski committed
27
      get ':id/repository/commits/:sha/statuses' do
28
29
30
31
        authorize!(:read_commit_status, user_project)

        not_found!('Commit') unless user_project.commit(params[:sha])

32
        pipelines = user_project.ci_pipelines.where(sha: params[:sha])
33
        statuses = ::CommitStatus.where(pipeline: pipelines)
34
        statuses = statuses.latest unless to_boolean(params[:all])
Kamil Trzcinski's avatar
Kamil Trzcinski committed
35
        statuses = statuses.where(ref: params[:ref]) if params[:ref].present?
Kamil Trzcinski's avatar
Kamil Trzcinski committed
36
        statuses = statuses.where(stage: params[:stage]) if params[:stage].present?
Kamil Trzcinski's avatar
Kamil Trzcinski committed
37
38
39
        statuses = statuses.where(name: params[:name]) if params[:name].present?
        present paginate(statuses), with: Entities::CommitStatus
      end
40
      # rubocop: enable CodeReuse/ActiveRecord
Kamil Trzcinski's avatar
Kamil Trzcinski committed
41

42
43
44
45
46
47
      desc 'Post status to a commit' do
        success Entities::CommitStatus
      end
      params do
        requires :sha,         type: String,  desc: 'The commit hash'
        requires :state,       type: String,  desc: 'The state of the status',
Douwe Maan's avatar
Douwe Maan committed
48
                               values: %w(pending running success failed canceled)
49
50
51
52
53
        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"'
        optional :context,     type: String,  desc: 'A string label to differentiate this status from the status of other systems. Default: "default"'
54
        optional :coverage,    type: Float,   desc: 'The total code coverage'
55
      end
56
      # rubocop: disable CodeReuse/ActiveRecord
Kamil Trzcinski's avatar
Kamil Trzcinski committed
57
      post ':id/statuses/:sha' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
58
        authorize! :create_commit_status, user_project
59

Kamil Trzcinski's avatar
Kamil Trzcinski committed
60
61
62
        commit = @project.commit(params[:sha])
        not_found! 'Commit' unless commit

63
        # Since the CommitStatus is attached to Ci::Pipeline (in the future Pipeline)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
64
65
66
67
68
69
70
        # We need to always have the pipeline object
        # To have a valid pipeline object that can be attached to specific MR
        # Other CI service needs to send `ref`
        # If we don't receive it, we will attach the CommitStatus to
        # the first found branch on that commit

        ref = params[:ref]
71
72
73
74
        ref ||= @project.repository.branch_names_contains(commit.sha).first
        not_found! 'References for commit' unless ref

        name = params[:name] || params[:context] || 'default'
Kamil Trzcinski's avatar
Kamil Trzcinski committed
75

76
77
        pipeline = @project.pipeline_for(ref, commit.sha)
        unless pipeline
78
          pipeline = @project.ci_pipelines.create!(
79
80
81
            source: :external,
            sha: commit.sha,
            ref: ref,
Shinya Maeda's avatar
Shinya Maeda committed
82
83
            user: current_user,
            protected: @project.protected_for?(ref))
84
        end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
85

86
        status = GenericCommitStatus.running_or_pending.find_or_initialize_by(
87
88
89
90
          project: @project,
          pipeline: pipeline,
          name: name,
          ref: ref,
Shinya Maeda's avatar
Shinya Maeda committed
91
92
          user: current_user,
          protected: @project.protected_for?(ref)
93
        )
Kamil Trzcinski's avatar
Kamil Trzcinski committed
94

95
96
97
98
        optional_attributes =
          attributes_for_keys(%w[target_url description coverage])

        status.update(optional_attributes) if optional_attributes.any?
99
100
        render_validation_error!(status) if status.invalid?

101
        begin
102
          case params[:state]
103
104
105
106
107
108
109
110
          when 'pending'
            status.enqueue!
          when 'running'
            status.enqueue
            status.run!
          when 'success'
            status.success!
          when 'failed'
Shinya Maeda's avatar
Shinya Maeda committed
111
            status.drop!(:api_failure)
112
113
114
115
116
          when 'canceled'
            status.cancel!
          else
            render_api_error!('invalid state', 400)
          end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
117

118
119
120
          MergeRequest.where(source_project: @project, source_branch: ref)
            .update_all(head_pipeline_id: pipeline) if pipeline.latest?

Kamil Trzcinski's avatar
Kamil Trzcinski committed
121
          present status, with: Entities::CommitStatus
122
123
        rescue StateMachines::InvalidTransition => e
          render_api_error!(e.message, 400)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
124
125
        end
      end
126
      # rubocop: enable CodeReuse/ActiveRecord
Kamil Trzcinski's avatar
Kamil Trzcinski committed
127
128
129
    end
  end
end