Commit fbd3b3d8 authored by Shinya Maeda's avatar Shinya Maeda

Add API support for pipeline schedule

parent a16cbab3
...@@ -40,6 +40,10 @@ module Ci ...@@ -40,6 +40,10 @@ module Ci
self.next_run_at = Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now) self.next_run_at = Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now)
end end
def last_pipeline
self.pipelines&.last
end
def schedule_next_run! def schedule_next_run!
save! # with set_next_run_at save! # with set_next_run_at
rescue ActiveRecord::RecordInvalid rescue ActiveRecord::RecordInvalid
......
...@@ -110,6 +110,7 @@ module API ...@@ -110,6 +110,7 @@ module API
mount ::API::Notes mount ::API::Notes
mount ::API::NotificationSettings mount ::API::NotificationSettings
mount ::API::Pipelines mount ::API::Pipelines
mount ::API::PipelineSchedules
mount ::API::ProjectHooks mount ::API::ProjectHooks
mount ::API::Projects mount ::API::Projects
mount ::API::ProjectSnippets mount ::API::ProjectSnippets
......
...@@ -686,6 +686,14 @@ module API ...@@ -686,6 +686,14 @@ module API
expose :coverage expose :coverage
end 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 class EnvironmentBasic < Grape::Entity
expose :id, :name, :slug, :external_url expose :id, :name, :slug, :external_url
end end
......
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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment