Skip to content
Snippets Groups Projects
Commit fbd3b3d8 authored by Shinya Maeda's avatar Shinya Maeda
Browse files

Add API support for pipeline schedule

parent a16cbab3
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -40,6 +40,10 @@ module Ci
self.next_run_at = Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now)
end
 
def last_pipeline
self.pipelines&.last
end
def schedule_next_run!
save! # with set_next_run_at
rescue ActiveRecord::RecordInvalid
Loading
Loading
Loading
Loading
@@ -110,6 +110,7 @@ module API
mount ::API::Notes
mount ::API::NotificationSettings
mount ::API::Pipelines
mount ::API::PipelineSchedules
mount ::API::ProjectHooks
mount ::API::Projects
mount ::API::ProjectSnippets
Loading
Loading
Loading
Loading
@@ -686,6 +686,14 @@ module API
expose :coverage
end
 
class PipelineSchedule < Grape::Entity
expose :id
expose :description, :ref, :cron, :cron_timezone, :next_run_at, :active
expose :created_at, :updated_at, :deleted_at
expose :last_pipeline, using: Entities::Pipeline, if: -> (pipeline_schedule, opts) { pipeline_schedule.last_pipeline.present? }
expose :owner, using: Entities::UserBasic
end
class EnvironmentBasic < Grape::Entity
expose :id, :name, :slug, :external_url
end
Loading
Loading
module API
class PipelineSchedules < Grape::API
include PaginationParams
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get pipeline_schedules list' do
success Entities::PipelineSchedule
end
params do
use :pagination
end
get ':id/pipeline_schedules' do
authenticate!
authorize! :read_pipeline_schedule, user_project
pipeline_schedules = user_project.pipeline_schedules
present paginate(pipeline_schedules), with: Entities::PipelineSchedule
end
desc 'Get specific pipeline_schedule of a project' do
success Entities::PipelineSchedule
end
params do
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline_schedule ID'
end
get ':id/pipeline_schedules/:pipeline_schedule_id' do
authenticate!
authorize! :read_pipeline_schedule, user_project
pipeline_schedule = user_project.pipeline_schedules.find(params.delete(:pipeline_schedule_id))
return not_found!('PipelineSchedule') unless pipeline_schedule
present pipeline_schedule, with: Entities::PipelineSchedule
end
desc 'Create a pipeline_schedule' do
success Entities::PipelineSchedule
end
params do
requires :description, type: String, desc: 'The pipeline_schedule description'
requires :ref, type: String, desc: 'The pipeline_schedule ref'
requires :cron, type: String, desc: 'The pipeline_schedule cron'
requires :cron_timezone, type: String, desc: 'The pipeline_schedule cron_timezone'
requires :active, type: Boolean, desc: 'The pipeline_schedule active'
end
post ':id/pipeline_schedules' do
authenticate!
authorize! :create_pipeline_schedule, user_project
pipeline_schedule = user_project.pipeline_schedules.create(
declared_params(include_missing: false).merge(owner: current_user))
if pipeline_schedule.valid?
present pipeline_schedule, with: Entities::PipelineSchedule
else
render_validation_error!(pipeline_schedule)
end
end
desc 'Update a pipeline_schedule' do
success Entities::PipelineSchedule
end
params do
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline_schedule ID'
optional :description, type: String, desc: 'The pipeline_schedule description'
optional :ref, type: String, desc: 'The pipeline_schedule ref'
optional :cron, type: String, desc: 'The pipeline_schedule cron'
optional :cron_timezone, type: String, desc: 'The pipeline_schedule cron_timezone'
optional :active, type: Boolean, desc: 'The pipeline_schedule active'
end
put ':id/pipeline_schedules/:pipeline_schedule_id' do
authenticate!
authorize! :create_pipeline_schedule, user_project
pipeline_schedule = user_project.pipeline_schedules.find(params.delete(:pipeline_schedule_id))
return not_found!('PipelineSchedule') unless pipeline_schedule
if pipeline_schedule.update(declared_params(include_missing: false))
present pipeline_schedule, with: Entities::PipelineSchedule
else
render_validation_error!(pipeline_schedule)
end
end
desc 'Take ownership of pipeline_schedule' do
success Entities::PipelineSchedule
end
params do
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline_schedule ID'
end
post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
authenticate!
authorize! :create_pipeline_schedule, user_project
pipeline_schedule = user_project.pipeline_schedules.find(params.delete(:pipeline_schedule_id))
return not_found!('PipelineSchedule') unless pipeline_schedule
if pipeline_schedule.update(owner: current_user)
status :ok
present pipeline_schedule, with: Entities::PipelineSchedule
else
render_validation_error!(pipeline_schedule)
end
end
desc 'Delete a pipeline_schedule' do
success Entities::PipelineSchedule
end
params do
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline_schedule ID'
end
delete ':id/pipeline_schedules/:pipeline_schedule_id' do
authenticate!
authorize! :admin_pipeline_schedule, user_project
pipeline_schedule = user_project.pipeline_schedules.find(params.delete(:pipeline_schedule_id))
return not_found!('PipelineSchedule') unless pipeline_schedule
present pipeline_schedule.destroy, with: Entities::PipelineSchedule
end
end
end
end
{
"type": "object",
"properties" : {
"id": { "type": "integer" },
"description": { "type": "string" },
"ref": { "type": "string" },
"cron": { "type": "string" },
"cron_timezone": { "type": "string" },
"next_run_at": { "type": "date" },
"active": { "type": "boolean" },
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
"deleted_at": { "type": "date" },
"last_pipeline": {
"type": ["object", "null"],
"properties": {
"id": { "type": "integer" },
"sha": { "type": "string" },
"ref": { "type": "string" },
"status": { "type": "string" },
"before_sha": { "type": ["string", "null"] },
"tag": { "type": ["boolean", "null"] },
"yaml_errors": { "type": ["string", "null"] },
"user": {
"type": ["object", "null"],
"properties": {
"name": { "type": "string" },
"username": { "type": "string" },
"id": { "type": "integer" },
"state": { "type": "string" },
"avatar_url": { "type": "uri" },
"web_url": { "type": "uri" }
},
"additionalProperties": false
},
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
"started_at": { "type": "date" },
"finished_at": { "type": "date" },
"committed_at": { "type": ["string", "null"] },
"duration": { "type": ["integer", "null"] },
"coverage": { "type": ["string", "null"] }
},
"additionalProperties": false
},
"owner": {
"type": "object",
"properties": {
"name": { "type": "string" },
"username": { "type": "string" },
"id": { "type": "integer" },
"state": { "type": "string" },
"avatar_url": { "type": "uri" },
"web_url": { "type": "uri" }
},
"additionalProperties": false
}
},
"required": [
"id", "description", "ref", "cron", "cron_timezone", "next_run_at",
"active", "created_at", "updated_at", "deleted_at", "owner"
],
"additionalProperties": false
}
{
"type": "array",
"items": { "$ref": "pipeline_schedule.json" }
}
require 'spec_helper'
describe API::PipelineSchedules do
let(:developer) { create(:user) }
let(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
before do
project.add_developer(developer)
end
describe 'GET /projects/:id/pipeline_schedules' do
context 'authenticated user with valid permissions' do
before do
create(:ci_pipeline_schedule, project: project, owner: developer)
.tap do |pipeline_schedule|
pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
end
end
it 'returns list of pipeline_schedules' do
get api("/projects/#{project.id}/pipeline_schedules", developer)
expect(response).to have_http_status(:ok)
expect(response).to include_pagination_headers
expect(response).to match_response_schema('pipeline_schedules')
end
end
context 'authenticated user with invalid permissions' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules", user)
expect(response).to have_http_status(:not_found)
end
end
context 'unauthenticated user' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules")
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'GET /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
let(:pipeline_schedule) do
create(:ci_pipeline_schedule, project: project, owner: developer)
.tap do |pipeline_schedule|
pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
end
end
context 'authenticated user with valid permissions' do
it 'returns pipeline_schedule details' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
expect(response).to have_http_status(:ok)
expect(response).to match_response_schema('pipeline_schedule')
end
it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
get api("/projects/#{project.id}/pipeline_schedules/-5", developer)
expect(response).to have_http_status(:not_found)
end
end
context 'authenticated user with invalid permissions' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
expect(response).to have_http_status(:not_found)
end
end
context 'unauthenticated user' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'POST /projects/:id/pipeline_schedules' do
let(:description) { 'pipeline_schedule' }
let(:ref) { 'master' }
let(:cron) { '* * * * *' }
let(:cron_timezone) { 'UTC' }
let(:active) { true }
context 'authenticated user with valid permissions' do
context 'with required parameters' do
it 'creates pipeline_schedule' do
expect do
post api("/projects/#{project.id}/pipeline_schedules", developer),
description: description, ref: ref, cron: cron,
cron_timezone: cron_timezone, active: active
end
.to change{project.pipeline_schedules.count}.by(1)
expect(response).to have_http_status(:created)
expect(response).to match_response_schema('pipeline_schedule')
expect(json_response['description']).to eq(description)
expect(json_response['ref']).to eq(ref)
expect(json_response['cron']).to eq(cron)
expect(json_response['cron_timezone']).to eq(cron_timezone)
expect(json_response['active']).to eq(active)
end
end
context 'without required parameters' do
it 'does not create pipeline_schedule' do
post api("/projects/#{project.id}/pipeline_schedules", developer)
expect(response).to have_http_status(:bad_request)
end
end
end
context 'authenticated user with invalid permissions' do
it 'does not create pipeline_schedule' do
post api("/projects/#{project.id}/pipeline_schedules", user),
description: description, ref: ref, cron: cron,
cron_timezone: cron_timezone, active: active
expect(response).to have_http_status(:not_found)
end
end
context 'unauthenticated user' do
it 'does not create pipeline_schedule' do
post api("/projects/#{project.id}/pipeline_schedules"),
description: description, ref: ref, cron: cron,
cron_timezone: cron_timezone, active: active
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
let(:pipeline_schedule) do
create(:ci_pipeline_schedule, project: project, owner: developer)
end
context 'authenticated user with valid permissions' do
let(:new_ref) { 'patch-x' }
it 'updates ref' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
ref: new_ref
expect(response).to have_http_status(:ok)
expect(response).to match_response_schema('pipeline_schedule')
expect(json_response['ref']).to eq(new_ref)
end
let(:new_cron) { '1 2 3 4 *' }
it 'updates cron' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
cron: new_cron
pipeline_schedule.reload
expect(response).to have_http_status(:ok)
expect(response).to match_response_schema('pipeline_schedule')
expect(json_response['cron']).to eq(new_cron)
expect(pipeline_schedule.next_run_at.min).to eq(1)
expect(pipeline_schedule.next_run_at.hour).to eq(2)
expect(pipeline_schedule.next_run_at.day).to eq(3)
expect(pipeline_schedule.next_run_at.month).to eq(4)
end
end
context 'authenticated user with invalid permissions' do
it 'does not update pipeline_schedule' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
expect(response).to have_http_status(:not_found)
end
end
context 'unauthenticated user' do
it 'does not update pipeline_schedule' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'POST /projects/:id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
let(:pipeline_schedule) do
create(:ci_pipeline_schedule, project: project, owner: developer)
end
context 'authenticated user with valid permissions' do
let(:developer2) { create(:user) }
before do
project.add_developer(developer2)
end
it 'updates owner' do
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", developer2)
pipeline_schedule.reload
expect(response).to have_http_status(:ok)
expect(response).to match_response_schema('pipeline_schedule')
expect(pipeline_schedule.owner).to eq(developer2)
end
end
context 'authenticated user with invalid permissions' do
it 'does not update owner' do
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", user)
expect(response).to have_http_status(:not_found)
end
end
context 'unauthenticated user' do
it 'does not update owner' do
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership")
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
let(:master) { create(:user) }
let!(:pipeline_schedule) do
create(:ci_pipeline_schedule, project: project, owner: developer)
end
before do
project.add_master(master)
end
context 'authenticated user with valid permissions' do
it 'deletes pipeline_schedule' do
expect do
delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", master)
end.to change{project.pipeline_schedules.count}.by(-1)
expect(response).to have_http_status(:ok)
expect(response).to match_response_schema('pipeline_schedule')
end
it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
delete api("/projects/#{project.id}/pipeline_schedules/-5", master)
expect(response).to have_http_status(:not_found)
end
end
context 'authenticated user with invalid permissions' do
it 'does not delete pipeline_schedule' do
delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not delete pipeline_schedule' do
delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
expect(response).to have_http_status(401)
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment