Read about our upcoming Code of Conduct on this issue

jobs.rb 5.81 KB
Newer Older
1
2
# frozen_string_literal: true

3
module API
4
  class Jobs < Grape::API
5
6
    include PaginationParams

7
8
    before { authenticate! }

Robert Schilling's avatar
Robert Schilling committed
9
10
11
    params do
      requires :id, type: String, desc: 'The ID of a project'
    end
12
    resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
Robert Schilling's avatar
Robert Schilling committed
13
14
15
      helpers do
        params :optional_scope do
          optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
16
                           values: ::CommitStatus::AVAILABLE_STATUSES,
Robert Schilling's avatar
Robert Schilling committed
17
                           coerce_with: ->(scope) {
18
19
                             case scope
                             when String
Robert Schilling's avatar
Robert Schilling committed
20
                               [scope]
Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
21
                             when ::Hash
Robert Schilling's avatar
Robert Schilling committed
22
                               scope.values
Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
23
                             when ::Array
24
                               scope
Robert Schilling's avatar
Robert Schilling committed
25
26
27
28
29
30
31
                             else
                               ['unknown']
                             end
                           }
        end
      end

32
33
      desc 'Get a projects jobs' do
        success Entities::Job
Robert Schilling's avatar
Robert Schilling committed
34
35
36
      end
      params do
        use :optional_scope
37
        use :pagination
Robert Schilling's avatar
Robert Schilling committed
38
      end
39
      # rubocop: disable CodeReuse/ActiveRecord
40
      get ':id/jobs' do
41
42
        builds = user_project.builds.order('id DESC')
        builds = filter_builds(builds, params[:scope])
43

44
        builds = builds.preload(:user, :job_artifacts_archive, :job_artifacts, :runner, pipeline: :project)
45
        present paginate(builds), with: Entities::Job
46
      end
47
      # rubocop: enable CodeReuse/ActiveRecord
48

49
50
51
52
53
54
55
56
      desc 'Get pipeline jobs' do
        success Entities::Job
      end
      params do
        requires :pipeline_id, type: Integer,  desc: 'The pipeline ID'
        use :optional_scope
        use :pagination
      end
57
      # rubocop: disable CodeReuse/ActiveRecord
58
      get ':id/pipelines/:pipeline_id/jobs' do
59
        pipeline = user_project.ci_pipelines.find(params[:pipeline_id])
60
61
        builds = pipeline.builds
        builds = filter_builds(builds, params[:scope])
62
        builds = builds.preload(:job_artifacts_archive, :job_artifacts, project: [:namespace])
63

64
        present paginate(builds), with: Entities::Job
65
      end
66
      # rubocop: enable CodeReuse/ActiveRecord
67

68
69
      desc 'Get a specific job of a project' do
        success Entities::Job
Robert Schilling's avatar
Robert Schilling committed
70
71
      end
      params do
72
        requires :job_id, type: Integer, desc: 'The ID of a job'
Robert Schilling's avatar
Robert Schilling committed
73
      end
74
      get ':id/jobs/:job_id' do
75
76
        authorize_read_builds!

77
        build = find_build!(params[:job_id])
78

79
        present build, with: Entities::Job
80
81
      end

82
      # TODO: We should use `present_disk_file!` and leave this implementation for backward compatibility (when build trace
83
84
      #       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.
85
      desc 'Get a trace of a specific job of a project'
Robert Schilling's avatar
Robert Schilling committed
86
      params do
87
        requires :job_id, type: Integer, desc: 'The ID of a job'
Robert Schilling's avatar
Robert Schilling committed
88
      end
89
      get ':id/jobs/:job_id/trace' do
90
91
        authorize_read_builds!

92
        build = find_build!(params[:job_id])
Tomasz Maczukin's avatar
Tomasz Maczukin committed
93
94
95
96

        header 'Content-Disposition', "infile; filename=\"#{build.id}.log\""
        content_type 'text/plain'
        env['api.format'] = :binary
97

98
        trace = build.trace.raw
Tomasz Maczukin's avatar
Tomasz Maczukin committed
99
        body trace
100
      end
101

102
103
      desc 'Cancel a specific job of a project' do
        success Entities::Job
Robert Schilling's avatar
Robert Schilling committed
104
105
      end
      params do
106
        requires :job_id, type: Integer, desc: 'The ID of a job'
Robert Schilling's avatar
Robert Schilling committed
107
      end
108
      post ':id/jobs/:job_id/cancel' do
109
        authorize_update_builds!
110

111
        build = find_build!(params[:job_id])
112
        authorize!(:update_build, build)
113
114
115

        build.cancel

116
        present build, with: Entities::Job
117
118
      end

Robert Schilling's avatar
Robert Schilling committed
119
      desc 'Retry a specific build of a project' do
120
        success Entities::Job
Robert Schilling's avatar
Robert Schilling committed
121
122
      end
      params do
123
        requires :job_id, type: Integer, desc: 'The ID of a build'
Robert Schilling's avatar
Robert Schilling committed
124
      end
125
      post ':id/jobs/:job_id/retry' do
126
        authorize_update_builds!
127

128
        build = find_build!(params[:job_id])
129
        authorize!(:update_build, build)
130
        break forbidden!('Job is not retryable') unless build.retryable?
131

132
        build = Ci::Build.retry(build, current_user)
133

134
        present build, with: Entities::Job
135
      end
136

137
138
      desc 'Erase job (remove artifacts and the trace)' do
        success Entities::Job
Robert Schilling's avatar
Robert Schilling committed
139
140
      end
      params do
141
        requires :job_id, type: Integer, desc: 'The ID of a build'
Robert Schilling's avatar
Robert Schilling committed
142
      end
143
      post ':id/jobs/:job_id/erase' do
144
        authorize_update_builds!
145

146
        build = find_build!(params[:job_id])
Shinya Maeda's avatar
Shinya Maeda committed
147
        authorize!(:erase_build, build)
148
        break forbidden!('Job is not erasable!') unless build.erasable?
149

150
        build.erase(erased_by: current_user)
151
        present build, with: Entities::Job
152
      end
153

154
      desc 'Trigger a actionable job (manual, delayed, etc)' do
155
        success Entities::Job
Z.J. van de Weg's avatar
Z.J. van de Weg committed
156
157
158
        detail 'This feature was added in GitLab 8.11'
      end
      params do
159
        requires :job_id, type: Integer, desc: 'The ID of a Job'
Z.J. van de Weg's avatar
Z.J. van de Weg committed
160
      end
161
      post ":id/jobs/:job_id/play" do
Z.J. van de Weg's avatar
Z.J. van de Weg committed
162
163
        authorize_read_builds!

164
        build = find_build!(params[:job_id])
Z.J. van de Weg's avatar
Z.J. van de Weg committed
165

166
        authorize!(:update_build, build)
Filipa Lacerda's avatar
Filipa Lacerda committed
167
        bad_request!("Unplayable Job") unless build.playable?
Z.J. van de Weg's avatar
Z.J. van de Weg committed
168
169
170
171

        build.play(current_user)

        status 200
172
        present build, with: Entities::Job
Z.J. van de Weg's avatar
Z.J. van de Weg committed
173
      end
174
175
176
    end

    helpers do
177
      # rubocop: disable CodeReuse/ActiveRecord
178
      def filter_builds(builds, scope)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
179
180
        return builds if scope.nil? || scope.empty?

181
        available_statuses = ::CommitStatus::AVAILABLE_STATUSES
182

Tomasz Maczukin's avatar
Tomasz Maczukin committed
183
184
        unknown = scope - available_statuses
        render_api_error!('Scope contains invalid value(s)', 400) unless unknown.empty?
185

Tomasz Maczukin's avatar
Tomasz Maczukin committed
186
        builds.where(status: available_statuses && scope)
187
      end
188
      # rubocop: enable CodeReuse/ActiveRecord
189
190
191
    end
  end
end