runners.rb 6.42 KB
Newer Older
1 2
module API
  class Runners < Grape::API
3 4
    include PaginationParams

5 6 7
    before { authenticate! }

    resource :runners do
Robert Schilling's avatar
Robert Schilling committed
8 9 10 11 12 13
      desc 'Get runners available for user' do
        success Entities::Runner
      end
      params do
        optional :scope, type: String, values: %w[active paused online],
                         desc: 'The scope of specific runners to show'
14
        use :pagination
Robert Schilling's avatar
Robert Schilling committed
15
      end
16
      get do
Douwe Maan's avatar
Douwe Maan committed
17
        runners = filter_runners(current_user.ci_authorized_runners, params[:scope], without: %w(specific shared))
18 19
        present paginate(runners), with: Entities::Runner
      end
20

Robert Schilling's avatar
Robert Schilling committed
21 22 23 24 25 26
      desc 'Get all runners - shared and specific' do
        success Entities::Runner
      end
      params do
        optional :scope, type: String, values: %w[active paused online specific shared],
                         desc: 'The scope of specific runners to show'
27
        use :pagination
Robert Schilling's avatar
Robert Schilling committed
28
      end
29 30 31
      get 'all' do
        authenticated_as_admin!
        runners = filter_runners(Ci::Runner.all, params[:scope])
32 33 34
        present paginate(runners), with: Entities::Runner
      end

Robert Schilling's avatar
Robert Schilling committed
35 36 37 38 39 40
      desc "Get runner's details" do
        success Entities::RunnerDetails
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the runner'
      end
41 42
      get ':id' do
        runner = get_runner(params[:id])
43
        authenticate_show_runner!(runner)
44

45
        present runner, with: Entities::RunnerDetails, current_user: current_user
46 47
      end

Robert Schilling's avatar
Robert Schilling committed
48 49 50 51 52 53 54 55 56 57 58 59
      desc "Update runner's details" do
        success Entities::RunnerDetails
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the runner'
        optional :description, type: String, desc: 'The description of the runner'
        optional :active, type: Boolean, desc: 'The state of a runner'
        optional :tag_list, type: Array[String], desc: 'The list of tags for a runner'
        optional :run_untagged, type: Boolean, desc: 'Flag indicating the runner can execute untagged jobs'
        optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked'
        at_least_one_of :description, :active, :tag_list, :run_untagged, :locked
      end
60
      put ':id' do
Robert Schilling's avatar
Robert Schilling committed
61
        runner = get_runner(params.delete(:id))
62
        authenticate_update_runner!(runner)
63
        update_service = Ci::UpdateRunnerService.new(runner)
64

65
        if update_service.update(declared_params(include_missing: false))
66
          present runner, with: Entities::RunnerDetails, current_user: current_user
67 68 69 70 71
        else
          render_validation_error!(runner)
        end
      end

Robert Schilling's avatar
Robert Schilling committed
72 73 74 75 76 77
      desc 'Remove a runner' do
        success Entities::Runner
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the runner'
      end
78 79
      delete ':id' do
        runner = get_runner(params[:id])
80

81
        authenticate_delete_runner!(runner)
82

83
        destroy_conditionally!(runner)
84 85 86
      end
    end

Robert Schilling's avatar
Robert Schilling committed
87 88 89
    params do
      requires :id, type: String, desc: 'The ID of a project'
    end
90
    resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
91 92
      before { authorize_admin_project }

Robert Schilling's avatar
Robert Schilling committed
93 94 95 96 97 98
      desc 'Get runners available for project' do
        success Entities::Runner
      end
      params do
        optional :scope, type: String, values: %w[active paused online specific shared],
                         desc: 'The scope of specific runners to show'
99
        use :pagination
Robert Schilling's avatar
Robert Schilling committed
100
      end
101 102 103 104
      get ':id/runners' do
        runners = filter_runners(Ci::Runner.owned_or_shared(user_project.id), params[:scope])
        present paginate(runners), with: Entities::Runner
      end
105

Robert Schilling's avatar
Robert Schilling committed
106 107 108 109 110 111
      desc 'Enable a runner for a project' do
        success Entities::Runner
      end
      params do
        requires :runner_id, type: Integer, desc: 'The ID of the runner'
      end
112
      post ':id/runners' do
113
        runner = get_runner(params[:runner_id])
114
        authenticate_enable_runner!(runner)
115

116 117 118
        runner_project = runner.assign_to(user_project)

        if runner_project.persisted?
119 120 121 122
          present runner, with: Entities::Runner
        else
          conflict!("Runner was already enabled for this project")
        end
123 124
      end

Robert Schilling's avatar
Robert Schilling committed
125 126 127 128 129 130
      desc "Disable project's runner" do
        success Entities::Runner
      end
      params do
        requires :runner_id, type: Integer, desc: 'The ID of the runner'
      end
131 132 133 134 135
      delete ':id/runners/:runner_id' do
        runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id])
        not_found!('Runner') unless runner_project

        runner = runner_project.runner
136
        forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
137

138
        destroy_conditionally!(runner_project)
139
      end
140 141 142
    end

    helpers do
Tomasz Maczukin's avatar
Tomasz Maczukin committed
143
      def filter_runners(runners, scope, options = {})
144 145 146
        return runners unless scope.present?

        available_scopes = ::Ci::Runner::AVAILABLE_SCOPES
Tomasz Maczukin's avatar
Tomasz Maczukin committed
147 148 149 150
        if options[:without]
          available_scopes = available_scopes - options[:without]
        end

151
        if (available_scopes & [scope]).empty?
152 153
          render_api_error!('Scope contains invalid value', 400)
        end
154

155
        runners.public_send(scope) # rubocop:disable GitlabSecurity/PublicSend
156 157 158 159 160 161 162 163
      end

      def get_runner(id)
        runner = Ci::Runner.find(id)
        not_found!('Runner') unless runner
        runner
      end

164
      def authenticate_show_runner!(runner)
165
        return if runner.is_shared || current_user.admin?
166
        forbidden!("No access granted") unless user_can_access_runner?(runner)
167 168
      end

169
      def authenticate_update_runner!(runner)
170
        return if current_user.admin?
171 172
        forbidden!("Runner is shared") if runner.is_shared?
        forbidden!("No access granted") unless user_can_access_runner?(runner)
173 174
      end

175
      def authenticate_delete_runner!(runner)
176
        return if current_user.admin?
177 178 179
        forbidden!("Runner is shared") if runner.is_shared?
        forbidden!("Runner associated with more than one project") if runner.projects.count > 1
        forbidden!("No access granted") unless user_can_access_runner?(runner)
180 181
      end

182 183
      def authenticate_enable_runner!(runner)
        forbidden!("Runner is shared") if runner.is_shared?
184
        forbidden!("Runner is locked") if runner.locked?
185
        return if current_user.admin?
186
        forbidden!("No access granted") unless user_can_access_runner?(runner)
187 188
      end

189
      def user_can_access_runner?(runner)
190
        current_user.ci_authorized_runners.exists?(runner.id)
191 192 193 194
      end
    end
  end
end