builds.rb 7.97 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
module API
  module V3
    class Builds < Grape::API
      include PaginationParams

      before { authenticate! }

      params do
        requires :id, type: String, desc: 'The ID of a project'
      end
      resource :projects do
        helpers do
          params :optional_scope do
            optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
Toon Claes's avatar
Toon Claes committed
15 16 17 18 19 20 21 22 23 24
                             values:  %w(pending running failed success canceled skipped),
                             coerce_with: ->(scope) {
                                            if scope.is_a?(String)
                                              [scope]
                                            elsif   scope.is_a?(Hashie::Mash)
                                              scope.values
                                            else
                                              ['unknown']
                                            end
                                          }
25 26 27 28
          end
        end

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

39
          present paginate(builds), with: ::API::V3::Entities::Build
40 41 42
        end

        desc 'Get builds for a specific commit of a project' do
Toon Claes's avatar
Toon Claes committed
43
          success ::API::V3::Entities::Build
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
        end
        params do
          requires :sha, type: String, desc: 'The SHA id of a commit'
          use :optional_scope
          use :pagination
        end
        get ':id/repository/commits/:sha/builds' do
          authorize_read_builds!

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

          pipelines = user_project.pipelines.where(sha: params[:sha])
          builds = user_project.builds.where(pipeline: pipelines).order('id DESC')
          builds = filter_builds(builds, params[:scope])

59
          present paginate(builds), with: ::API::V3::Entities::Build
60 61 62
        end

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

          build = get_build!(params[:build_id])

73
          present build, with: ::API::V3::Entities::Build
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
        end

        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
        get ':id/builds/:build_id/artifacts' do
          authorize_read_builds!

          build = get_build!(params[:build_id])

          present_artifacts!(build.artifacts_file)
        end

        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
        get ':id/builds/artifacts/:ref_name/download',
          requirements: { ref_name: /.+/ } do
          authorize_read_builds!

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

          present_artifacts!(latest_build.artifacts_file)
        end

        # 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.
        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
        get ':id/builds/:build_id/trace' do
          authorize_read_builds!

          build = get_build!(params[:build_id])

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

123
          trace = build.trace.raw
124 125 126 127
          body trace
        end

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

          build = get_build!(params[:build_id])
137
          authorize!(:update_build, build)
138 139 140

          build.cancel

141
          present build, with: ::API::V3::Entities::Build
142 143 144
        end

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

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

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

159
          present build, with: ::API::V3::Entities::Build
160 161 162
        end

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

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

          build.erase(erased_by: current_user)
176
          present build, with: ::API::V3::Entities::Build
177 178 179
        end

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

          build = get_build!(params[:build_id])
189
          authorize!(:update_build, build)
190 191 192 193 194
          return not_found!(build) unless build.artifacts?

          build.keep_artifacts!

          status 200
195
          present build, with: ::API::V3::Entities::Build
196 197 198
        end

        desc 'Trigger a manual build' do
Toon Claes's avatar
Toon Claes committed
199
          success ::API::V3::Entities::Build
200 201 202 203 204 205 206 207 208
          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])
209
          authorize!(:update_build, build)
210 211 212 213 214
          bad_request!("Unplayable Job") unless build.playable?

          build.play(current_user)

          status 200
215
          present build, with: ::API::V3::Entities::Build
216 217 218 219
        end
      end

      helpers do
220
        def find_build(id)
221 222 223 224
          user_project.builds.find_by(id: id.to_i)
        end

        def get_build!(id)
225
          find_build(id) || not_found!
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
        end

        def filter_builds(builds, scope)
          return builds if scope.nil? || scope.empty?

          available_statuses = ::CommitStatus::AVAILABLE_STATUSES

          unknown = scope - available_statuses
          render_api_error!('Scope contains invalid value(s)', 400) unless unknown.empty?

          builds.where(status: available_statuses && scope)
        end

        def authorize_read_builds!
          authorize! :read_build, user_project
        end

        def authorize_update_builds!
          authorize! :update_build, user_project
        end
      end
    end
  end
end