builds.rb 8.17 KB
Newer Older
1
2
module API
  class Builds < Grape::API
3
4
    include PaginationParams

5
6
    before { authenticate! }

Robert Schilling's avatar
Robert Schilling committed
7
8
9
    params do
      requires :id, type: String, desc: 'The ID of a project'
    end
10
    resource :projects do
Robert Schilling's avatar
Robert Schilling committed
11
12
13
      helpers do
        params :optional_scope do
          optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
14
                           values: ::CommitStatus::AVAILABLE_STATUSES,
Robert Schilling's avatar
Robert Schilling committed
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
                           coerce_with: ->(scope) {
                             if scope.is_a?(String)
                               [scope]
                             elsif scope.is_a?(Hashie::Mash)
                               scope.values
                             else
                               ['unknown']
                             end
                           }
        end
      end

      desc 'Get a project builds' do
        success Entities::Build
      end
      params do
        use :optional_scope
32
        use :pagination
Robert Schilling's avatar
Robert Schilling committed
33
      end
34
      get ':id/builds' do
35
36
        builds = user_project.builds.order('id DESC')
        builds = filter_builds(builds, params[:scope])
37
38

        present paginate(builds), with: Entities::Build,
39
                                  user_can_download_artifacts: can?(current_user, :read_build, user_project)
40
      end
41

Robert Schilling's avatar
Robert Schilling committed
42
43
44
45
      desc 'Get builds for a specific commit of a project' do
        success Entities::Build
      end
      params do
46
        requires :sha, type: String, desc: 'The SHA id of a commit'
Robert Schilling's avatar
Robert Schilling committed
47
        use :optional_scope
48
        use :pagination
Robert Schilling's avatar
Robert Schilling committed
49
      end
50
      get ':id/repository/commits/:sha/builds' do
51
52
        authorize_read_builds!

53
54
        return not_found! unless user_project.commit(params[:sha])

55
56
        pipelines = user_project.pipelines.where(sha: params[:sha])
        builds = user_project.builds.where(pipeline: pipelines).order('id DESC')
57
        builds = filter_builds(builds, params[:scope])
Tomasz Maczukin's avatar
Tomasz Maczukin committed
58

59
        present paginate(builds), with: Entities::Build,
60
                                  user_can_download_artifacts: can?(current_user, :read_build, user_project)
61
62
      end

Robert Schilling's avatar
Robert Schilling committed
63
64
65
66
67
68
      desc 'Get a specific build of a project' do
        success Entities::Build
      end
      params do
        requires :build_id, type: Integer, desc: 'The ID of a build'
      end
69
      get ':id/builds/:build_id' do
70
71
        authorize_read_builds!

72
        build = get_build!(params[:build_id])
73

74
        present build, with: Entities::Build,
75
                       user_can_download_artifacts: can?(current_user, :read_build, user_project)
76
77
      end

Robert Schilling's avatar
Robert Schilling committed
78
79
80
81
82
83
      desc 'Download the artifacts file from build' do
        detail 'This feature was introduced in GitLab 8.5'
      end
      params do
        requires :build_id, type: Integer, desc: 'The ID of a build'
      end
84
85
86
      get ':id/builds/:build_id/artifacts' do
        authorize_read_builds!

87
        build = get_build!(params[:build_id])
88

Lin Jen-Shin's avatar
Lin Jen-Shin committed
89
        present_artifacts!(build.artifacts_file)
90
      end
91

Robert Schilling's avatar
Robert Schilling committed
92
93
94
95
96
97
98
      desc 'Download the artifacts file from build' do
        detail 'This feature was introduced in GitLab 8.10'
      end
      params do
        requires :ref_name, type: String, desc: 'The ref from repository'
        requires :job,      type: String, desc: 'The name for the build'
      end
99
100
      get ':id/builds/artifacts/:ref_name/download',
        requirements: { ref_name: /.+/ } do
101
102
        authorize_read_builds!

103
104
        builds = user_project.latest_successful_builds_for(params[:ref_name])
        latest_build = builds.find_by!(name: params[:job])
105

Lin Jen-Shin's avatar
Lin Jen-Shin committed
106
        present_artifacts!(latest_build.artifacts_file)
107
108
      end

109
110
111
      # TODO: We should use `present_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.
Robert Schilling's avatar
Robert Schilling committed
112
113
114
115
      desc 'Get a trace of a specific build of a project'
      params do
        requires :build_id, type: Integer, desc: 'The ID of a build'
      end
116
      get ':id/builds/:build_id/trace' do
117
118
        authorize_read_builds!

119
        build = get_build!(params[:build_id])
Tomasz Maczukin's avatar
Tomasz Maczukin committed
120
121
122
123

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

Tomasz Maczukin's avatar
Tomasz Maczukin committed
125
126
        trace = build.trace
        body trace
127
      end
128

Robert Schilling's avatar
Robert Schilling committed
129
130
131
132
133
134
      desc 'Cancel a specific build of a project' do
        success Entities::Build
      end
      params do
        requires :build_id, type: Integer, desc: 'The ID of a build'
      end
135
      post ':id/builds/:build_id/cancel' do
136
        authorize_update_builds!
137

138
        build = get_build!(params[:build_id])
139
140
141

        build.cancel

142
        present build, with: Entities::Build,
143
                       user_can_download_artifacts: can?(current_user, :read_build, user_project)
144
145
      end

Robert Schilling's avatar
Robert Schilling committed
146
147
148
149
150
151
      desc 'Retry a specific build of a project' do
        success Entities::Build
      end
      params do
        requires :build_id, type: Integer, desc: 'The ID of a build'
      end
152
      post ':id/builds/:build_id/retry' do
153
        authorize_update_builds!
154

155
        build = get_build!(params[:build_id])
156
        return forbidden!('Build is not retryable') unless build.retryable?
157

158
        build = Ci::Build.retry(build, current_user)
159

160
        present build, with: Entities::Build,
161
                       user_can_download_artifacts: can?(current_user, :read_build, user_project)
162
      end
163

Robert Schilling's avatar
Robert Schilling committed
164
165
166
167
168
169
      desc 'Erase build (remove artifacts and build trace)' do
        success Entities::Build
      end
      params do
        requires :build_id, type: Integer, desc: 'The ID of a build'
      end
170
      post ':id/builds/:build_id/erase' do
171
        authorize_update_builds!
172

173
        build = get_build!(params[:build_id])
174
        return forbidden!('Build is not erasable!') unless build.erasable?
175

176
        build.erase(erased_by: current_user)
177
178
179
        present build, with: Entities::Build,
                       user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
      end
180

Robert Schilling's avatar
Robert Schilling committed
181
182
183
184
185
186
      desc 'Keep the artifacts to prevent them from being deleted' do
        success Entities::Build
      end
      params do
        requires :build_id, type: Integer, desc: 'The ID of a build'
      end
187
188
189
      post ':id/builds/:build_id/artifacts/keep' do
        authorize_update_builds!

190
191
        build = get_build!(params[:build_id])
        return not_found!(build) unless build.artifacts?
192
193
194
195
196

        build.keep_artifacts!

        status 200
        present build, with: Entities::Build,
Kamil Trzcinski's avatar
Kamil Trzcinski committed
197
                       user_can_download_artifacts: can?(current_user, :read_build, user_project)
198
      end
Z.J. van de Weg's avatar
Z.J. van de Weg committed
199
200
201
202
203
204
205
206
207
208
209
210
211

      desc 'Trigger a manual build' do
        success Entities::Build
        detail 'This feature was added in GitLab 8.11'
      end
      params do
        requires :build_id, type: Integer, desc: 'The ID of a Build'
      end
      post ":id/builds/:build_id/play" do
        authorize_read_builds!

        build = get_build!(params[:build_id])

Filipa Lacerda's avatar
Filipa Lacerda committed
212
        bad_request!("Unplayable Job") unless build.playable?
Z.J. van de Weg's avatar
Z.J. van de Weg committed
213
214
215
216
217
218

        build.play(current_user)

        status 200
        present build, with: Entities::Build,
                       user_can_download_artifacts: can?(current_user, :read_build, user_project)
Z.J. van de Weg's avatar
Z.J. van de Weg committed
219
      end
220
221
222
223
    end

    helpers do
      def get_build(id)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
224
        user_project.builds.find_by(id: id.to_i)
225
      end
226

227
228
229
230
      def get_build!(id)
        get_build(id) || not_found!
      end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
231
      def present_artifacts!(artifacts_file)
232
233
234
235
236
237
238
239
240
        if !artifacts_file.file_storage?
          redirect_to(build.artifacts_file.url)
        elsif artifacts_file.exists?
          present_file!(artifacts_file.path, artifacts_file.filename)
        else
          not_found!
        end
      end

241
      def filter_builds(builds, scope)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
242
243
        return builds if scope.nil? || scope.empty?

244
        available_statuses = ::CommitStatus::AVAILABLE_STATUSES
245

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

Tomasz Maczukin's avatar
Tomasz Maczukin committed
249
        builds.where(status: available_statuses && scope)
250
      end
251

252
253
254
255
256
257
      def authorize_read_builds!
        authorize! :read_build, user_project
      end

      def authorize_update_builds!
        authorize! :update_build, user_project
258
      end
259
260
261
    end
  end
end