Commit 32d83ee7 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'master' into ux/suppress-ci-yml-warning

* master: (24 commits)
  Fix runners admin view
  Fix migrations
  Run builds from projects with enabled CI
  Use Gitlab::Git instead of Ci::Git
  Fix last specs
  Fix specs
  Fix after column rename
  Fix errors
  Update badge
  Finishing touches
  Fix triggers tests
  Rename columns and rename migrations
  Reimplement Trigger API
  Remove ci_ prefix from all ci related things
  Add runners token
  Migrate CI::Project to Project
  Fix indentation and BuildsEmailService
  Change default values
  Enhance migrate CI emails
  Fix issue tracker service
  ...

Conflicts:
	spec/features/commits_spec.rb
parents b8f67c5e c8102343
...@@ -21,6 +21,7 @@ v 8.3.0 (unreleased) ...@@ -21,6 +21,7 @@ v 8.3.0 (unreleased)
- Fire update hook from GitLab - Fire update hook from GitLab
- Style warning about mentioning many people in a comment - Style warning about mentioning many people in a comment
- Fix: sort milestones by due date once again (Greg Smethells) - Fix: sort milestones by due date once again (Greg Smethells)
- Migrate all CI::Services and CI::WebHooks to Services and WebHooks
- Don't show project fork event as "imported" - Don't show project fork event as "imported"
- Add API endpoint to fetch merge request commits list - Add API endpoint to fetch merge request commits list
- Expose events API with comment information and author info - Expose events API with comment information and author info
......
class Admin::BuildsController < Admin::ApplicationController
def index
@scope = params[:scope]
@all_builds = Ci::Build
@builds = @all_builds.order('created_at DESC')
@builds =
case @scope
when 'all'
@builds
when 'finished'
@builds.finished
else
@builds.running_or_pending.reverse_order
end
@builds = @builds.page(params[:page]).per(30)
end
def cancel_all
Ci::Build.running_or_pending.each(&:cancel)
redirect_to admin_builds_path
end
end
class Admin::RunnerProjectsController < Admin::ApplicationController
before_action :project, only: [:create]
def index
@runner_projects = project.runner_projects.all
@runner_project = project.runner_projects.new
end
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(@project, current_user)
redirect_to admin_runner_path(@runner)
else
redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
end
end
def destroy
rp = Ci::RunnerProject.find(params[:id])
runner = rp.runner
rp.destroy
redirect_to admin_runner_path(runner)
end
private
def project
@project = Project.find_with_namespace(
[params[:namespace_id], '/', params[:project_id]].join('')
)
@project || render_404
end
end
class Admin::RunnersController < Admin::ApplicationController
before_action :runner, except: :index
def index
@runners = Ci::Runner.order('id DESC')
@runners = @runners.search(params[:search]) if params[:search].present?
@runners = @runners.page(params[:page]).per(30)
@active_runners_cnt = Ci::Runner.online.count
end
def show
@builds = @runner.builds.order('id DESC').first(30)
@projects =
if params[:search].present?
::Project.search(params[:search])
else
Project.all
end
@projects = @projects.where.not(id: @runner.projects.select(:id)) if @runner.projects.any?
@projects = @projects.page(params[:page]).per(30)
end
def update
@runner.update_attributes(runner_params)
respond_to do |format|
format.js
format.html { redirect_to admin_runner_path(@runner) }
end
end
def destroy
@runner.destroy
redirect_to admin_runners_path
end
def resume
if @runner.update_attributes(active: true)
redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to admin_runners_path, alert: 'Runner was not updated.'
end
end
def pause
if @runner.update_attributes(active: false)
redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to admin_runners_path, alert: 'Runner was not updated.'
end
end
private
def runner
@runner ||= Ci::Runner.find(params[:id])
end
def runner_params
params.require(:runner).permit(:token, :description, :tag_list, :active)
end
end
module Ci
module Admin
class ApplicationController < Ci::ApplicationController
before_action :authenticate_user!
before_action :authenticate_admin!
layout "ci/admin"
end
end
end
module Ci
class Admin::ApplicationSettingsController < Ci::Admin::ApplicationController
before_action :set_application_setting
def show
end
def update
if @application_setting.update_attributes(application_setting_params)
redirect_to ci_admin_application_settings_path,
notice: 'Application settings saved successfully'
else
render :show
end
end
private
def set_application_setting
@application_setting = Ci::ApplicationSetting.current
@application_setting ||= Ci::ApplicationSetting.create_from_defaults
end
def application_setting_params
params.require(:application_setting).permit(
:all_broken_builds,
:add_pusher,
)
end
end
end
module Ci
class Admin::BuildsController < Ci::Admin::ApplicationController
def index
@scope = params[:scope]
@builds = Ci::Build.order('created_at DESC').page(params[:page]).per(30)
@builds =
case @scope
when "pending"
@builds.pending
when "running"
@builds.running
else
@builds
end
end
end
end
module Ci
class Admin::EventsController < Ci::Admin::ApplicationController
EVENTS_PER_PAGE = 50
def index
@events = Ci::Event.admin.order('created_at DESC').page(params[:page]).per(EVENTS_PER_PAGE)
end
end
end
module Ci
class Admin::ProjectsController < Ci::Admin::ApplicationController
def index
@projects = Ci::Project.ordered_by_last_commit_date.page(params[:page]).per(30)
end
def destroy
project.destroy
redirect_to ci_projects_url
end
protected
def project
@project ||= Ci::Project.find(params[:id])
end
end
end
module Ci
class Admin::RunnerProjectsController < Ci::Admin::ApplicationController
layout 'ci/project'
def index
@runner_projects = project.runner_projects.all
@runner_project = project.runner_projects.new
end
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(project, current_user)
redirect_to ci_admin_runner_path(@runner)
else
redirect_to ci_admin_runner_path(@runner), alert: 'Failed adding runner to project'
end
end
def destroy
rp = Ci::RunnerProject.find(params[:id])
runner = rp.runner
rp.destroy
redirect_to ci_admin_runner_path(runner)
end
private
def project
@project ||= Ci::Project.find(params[:project_id])
end
end
end
module Ci
class Admin::RunnersController < Ci::Admin::ApplicationController
before_action :runner, except: :index
def index
@runners = Ci::Runner.order('id DESC')
@runners = @runners.search(params[:search]) if params[:search].present?
@runners = @runners.page(params[:page]).per(30)
@active_runners_cnt = Ci::Runner.online.count
end
def show
@builds = @runner.builds.order('id DESC').first(30)
@projects = Ci::Project.all
if params[:search].present?
@gl_projects = ::Project.search(params[:search])
@projects = @projects.where(gitlab_id: @gl_projects.select(:id))
end
@projects = @projects.where("ci_projects.id NOT IN (?)", @runner.projects.pluck(:id)) if @runner.projects.any?
@projects = @projects.joins(:gl_project)
@projects = @projects.page(params[:page]).per(30)
end
def update
@runner.update_attributes(runner_params)
respond_to do |format|
format.js
format.html { redirect_to ci_admin_runner_path(@runner) }
end
end
def destroy
@runner.destroy
redirect_to ci_admin_runners_path
end
def resume
if @runner.update_attributes(active: true)
redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to ci_admin_runners_path, alert: 'Runner was not updated.'
end
end
def pause
if @runner.update_attributes(active: false)
redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to ci_admin_runners_path, alert: 'Runner was not updated.'
end
end
def assign_all
Ci::Project.unassigned(@runner).all.each do |project|
@runner.assign_to(project, current_user)
end
redirect_to ci_admin_runner_path(@runner), notice: "Runner was assigned to all projects"
end
private
def runner
@runner ||= Ci::Runner.find(params[:id])
end
def runner_params
params.require(:runner).permit(:token, :description, :tag_list, :active)
end
end
end
...@@ -4,24 +4,16 @@ module Ci ...@@ -4,24 +4,16 @@ module Ci
"app/helpers/ci" "app/helpers/ci"
end end
helper_method :gl_project
private private
def authenticate_token!
unless project.valid_token?(params[:token])
return head(403)
end
end
def authorize_access_project! def authorize_access_project!
unless can?(current_user, :read_project, gl_project) unless can?(current_user, :read_project, project)
return page_404 return page_404
end end
end end
def authorize_manage_builds! def authorize_manage_builds!
unless can?(current_user, :manage_builds, gl_project) unless can?(current_user, :manage_builds, project)
return page_404 return page_404
end end
end end
...@@ -31,7 +23,7 @@ module Ci ...@@ -31,7 +23,7 @@ module Ci
end end
def authorize_manage_project! def authorize_manage_project!
unless can?(current_user, :admin_project, gl_project) unless can?(current_user, :admin_project, project)
return page_404 return page_404
end end
end end
...@@ -58,9 +50,5 @@ module Ci ...@@ -58,9 +50,5 @@ module Ci
count: count count: count
} }
end end
def gl_project
::Project.find(@project.gitlab_id)
end
end end
end end
module Ci module Ci
class LintsController < Ci::ApplicationController class LintsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
def show def show
......
...@@ -3,13 +3,12 @@ module Ci ...@@ -3,13 +3,12 @@ module Ci
before_action :project, except: [:index] before_action :project, except: [:index]
before_action :authenticate_user!, except: [:index, :build, :badge] before_action :authenticate_user!, except: [:index, :build, :badge]
before_action :authorize_access_project!, except: [:index, :badge] before_action :authorize_access_project!, except: [:index, :badge]
before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
before_action :no_cache, only: [:badge] before_action :no_cache, only: [:badge]
protect_from_forgery protect_from_forgery
def show def show
# Temporary compatibility with CI badges pointing to CI project page # Temporary compatibility with CI badges pointing to CI project page
redirect_to namespace_project_path(project.gl_project.namespace, project.gl_project) redirect_to namespace_project_path(project.namespace, project)
end end
# Project status badge # Project status badge
...@@ -20,16 +19,10 @@ module Ci ...@@ -20,16 +19,10 @@ module Ci
send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml" send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml"
end end
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
redirect_to namespace_project_runners_path(project.gl_project.namespace, project.gl_project)
end
protected protected
def project def project
@project ||= Ci::Project.find(params[:id]) @project ||= Project.find_by(ci_id: params[:id].to_i)
end end
def no_cache def no_cache
......
module Ci
class RunnerProjectsController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :authorize_manage_project!
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(@project.gl_project)
if @runner.assign_to(project, current_user)
redirect_to path
else
redirect_to path, alert: 'Failed adding runner to project'
end
end
def destroy
runner_project = project.runner_projects.find(params[:id])
runner_project.destroy
redirect_to runners_path(@project.gl_project)
end
private
def project
@project ||= Ci::Project.find(params[:project_id])
end
end
end
...@@ -31,8 +31,4 @@ class Projects::ApplicationController < ApplicationController ...@@ -31,8 +31,4 @@ class Projects::ApplicationController < ApplicationController
def builds_enabled def builds_enabled
return render_404 unless @project.builds_enabled? return render_404 unless @project.builds_enabled?
end end
def ci_project
@ci_project ||= @project.ensure_gitlab_ci_project
end
end end
class Projects::BuildsController < Projects::ApplicationController class Projects::BuildsController < Projects::ApplicationController
before_action :ci_project
before_action :build, except: [:index, :cancel_all] before_action :build, except: [:index, :cancel_all]
before_action :authorize_manage_builds!, except: [:index, :show, :status] before_action :authorize_manage_builds!, except: [:index, :show, :status]
...@@ -9,7 +8,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -9,7 +8,7 @@ class Projects::BuildsController < Projects::ApplicationController
def index def index
@scope = params[:scope] @scope = params[:scope]
@all_builds = project.ci_builds @all_builds = project.builds
@builds = @all_builds.order('created_at DESC') @builds = @all_builds.order('created_at DESC')
@builds = @builds =
case @scope case @scope
...@@ -24,13 +23,13 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -24,13 +23,13 @@ class Projects::BuildsController < Projects::ApplicationController
end end
def cancel_all def cancel_all
@project.ci_builds.running_or_pending.each(&:cancel) @project.builds.running_or_pending.each(&:cancel)
redirect_to namespace_project_builds_path(project.namespace, project) redirect_to namespace_project_builds_path(project.namespace, project)
end end
def show def show
@builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC') @builds = @project.ci_commits.find_by_sha(@build.sha).builds.order('id DESC')
@builds = @builds.where("id not in (?)", @build.id) @builds = @builds.where("id not in (?)", @build.id)
@commit = @build.commit @commit = @build.commit
...@@ -77,7 +76,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -77,7 +76,7 @@ class Projects::BuildsController < Projects::ApplicationController
private private
def build def build
@build ||= ci_project.builds.unscoped.find_by!(id: params[:id]) @build ||= project.builds.unscoped.find_by!(id: params[:id])
end end
def artifacts_file def artifacts_file
...@@ -85,7 +84,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -85,7 +84,7 @@ class Projects::BuildsController < Projects::ApplicationController
end end
def build_path(build) def build_path(build)
namespace_project_build_path(build.gl_project.namespace, build.gl_project, build) namespace_project_build_path(build.project.namespace, build.project, build)
end end
def authorize_manage_builds! def authorize_manage_builds!
......
class Projects::CiServicesController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project!
layout "project_settings"
def index
@ci_project.build_missing_services
@services = @ci_project.services.reload
end
def edit
service
end
def update
if service.update_attributes(service_params)
redirect_to edit_namespace_project_ci_service_path(@project.namespace, @project, service.to_param)
else
render 'edit'
end
end
def test
last_build = @project.ci_builds.last
if service.execute(last_build)
message = { notice: 'We successfully tested the service' }
else
message = { alert: 'We tried to test the service but error occurred' }
end
redirect_back_or_default(options: message)
end
private
def service
@service ||= @ci_project.services.find { |service| service.to_param == params[:id] }
end
def service_params
params.require(:service).permit(
:type, :active, :webhook, :notify_only_broken_builds,
:email_recipients, :email_only_broken_builds, :email_add_pusher,
:hipchat_token, :hipchat_room, :hipchat_server
)
end
end
class Projects::CiSettingsController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project!
layout "project_settings"
def edit
end
def update
if ci_project.update_attributes(project_params)
Ci::EventService.new.change_project_settings(current_user, ci_project)
redirect_to edit_namespace_project_ci_settings_path(project.namespace, project), notice: 'Project was successfully updated.'
else
render action: "edit"
end
end
def destroy
ci_project.destroy
Ci::EventService.new.remove_project(current_user, ci_project)
project.gitlab_ci_service.update_attributes(active: false)
redirect_to project_path(project), notice: "CI was disabled for this project"
end
protected
def project_params
params.require(:project).permit(:path, :timeout, :timeout_in_minutes, :default_ref, :always_build,
:polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :email_recipients,
:email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token,
{ variables_attributes: [:id, :key, :value, :_destroy] })
end
end
class Projects::CiWebHooksController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project!
layout "project_settings"
def index
@web_hooks = @ci_project.web_hooks
@web_hook = Ci::WebHook.new
end
def create
@web_hook = @ci_project.web_hooks.new(web_hook_params)
@web_hook.save
if @web_hook.valid?
redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project)
else
@web_hooks = @ci_project.web_hooks.select(&:persisted?)
render :index
end
end
def test
Ci::TestHookService.new.execute(hook, current_user)
redirect_back_or_default(default: { action: 'index' })
end
def destroy
hook.destroy
redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project)
end
private
def hook
@web_hook ||= @ci_project.web_hooks.find(params[:id])
end
def web_hook_params
params.require(:web_hook).permit(:url)
end
end
...@@ -31,7 +31,6 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -31,7 +31,6 @@ class Projects::CommitController < Projects::ApplicationController
end end
def builds def builds
@ci_project = @project.gitlab_ci_project
end end
def cancel_builds def cancel_builds
......
...@@ -25,13 +25,11 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -25,13 +25,11 @@ class Projects::GraphsController < Projects::ApplicationController
end end
def ci def ci
ci_project = @project.gitlab_ci_project
@charts = {} @charts = {}
@charts[:week] = Ci::Charts::WeekChart.new(ci_project) @charts[:week] = Ci::Charts::WeekChart.new(project)
@charts[:month] = Ci::Charts::MonthChart.new(ci_project) @charts[:month] = Ci::Charts::MonthChart.new(project)
@charts[:year] = Ci::Charts::YearChart.new(ci_project) @charts[:year] = Ci::Charts::YearChart.new(project)
@charts[:build_times] = Ci::Charts::BuildTime.new(ci_project) @charts[:build_times] = Ci::Charts::BuildTime.new(project)
end end
def languages def languages
......
...@@ -53,6 +53,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -53,6 +53,7 @@ class Projects::HooksController < Projects::ApplicationController
def hook_params def hook_params
params.require(:hook).permit(:url, :push_events, :issues_events, params.require(:hook).permit(:url, :push_events, :issues_events,
:merge_requests_events, :tag_push_events, :note_events, :enable_ssl_verification) :merge_requests_events, :tag_push_events, :note_events,
:build_events, :enable_ssl_verification)
end end
end end
...@@ -81,8 +81,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -81,8 +81,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def builds def builds
@ci_project = @merge_request.source_project.gitlab_ci_project
respond_to do |format| respond_to do |format|
format.html { render 'show' } format.html { render 'show' }
format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } } format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } }
...@@ -106,7 +104,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -106,7 +104,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@first_commit = @merge_request.first_commit @first_commit = @merge_request.first_commit
@diffs = @merge_request.compare_diffs @diffs = @merge_request.compare_diffs
@ci_project = @source_project.gitlab_ci_project
@ci_commit = @merge_request.ci_commit @ci_commit = @merge_request.ci_commit
@statuses = @ci_commit.statuses if @ci_commit @statuses = @ci_commit.statuses if @ci_commit
......
class Projects::RunnerProjectsController < Projects::ApplicationController
before_action :authorize_admin_project!
layout 'project_settings'
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(project)
if @runner.assign_to(project, current_user)
redirect_to path
else
redirect_to path, alert: 'Failed adding runner to project'
end
end
def destroy
runner_project = project.runner_projects.find(params[:id])
runner_project.destroy
redirect_to runners_path(project)
end
end
class Projects::RunnersController < Projects::ApplicationController class Projects::RunnersController < Projects::ApplicationController
before_action :ci_project
before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show] before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
before_action :authorize_admin_project! before_action :authorize_admin_project!
layout 'project_settings' layout 'project_settings'
def index def index
@runners = @ci_project.runners.ordered @runners = project.runners.ordered
@specific_runners = current_user.ci_authorized_runners. @specific_runners = current_user.ci_authorized_runners.
where.not(id: @ci_project.runners). where.not(id: project.runners).
ordered.page(params[:page]).per(20) ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active @shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all) @shared_runners_count = @shared_runners.count(:all)
...@@ -26,7 +25,7 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -26,7 +25,7 @@ class Projects::RunnersController < Projects::ApplicationController
end end
def destroy def destroy
if @runner.only_for?(@ci_project) if @runner.only_for?(project)
@runner.destroy @runner.destroy
end end
...@@ -52,10 +51,16 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -52,10 +51,16 @@ class Projects::RunnersController < Projects::ApplicationController
def show def show
end end
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
redirect_to namespace_project_runners_path(project.namespace, project)
end
protected protected
def set_runner def set_runner
@runner ||= @ci_project.runners.find(params[:id]) @runner ||= project.runners.find(params[:id])
end end
def runner_params def runner_params
......
...@@ -6,7 +6,9 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -6,7 +6,9 @@ class Projects::ServicesController < Projects::ApplicationController
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels, :colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events, :push_events, :issues_events, :merge_requests_events, :tag_push_events,
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, :note_events, :build_events,
:notify_only_broken_builds, :add_pusher,
:send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color, :notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification] :server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
......
class Projects::TriggersController < Projects::ApplicationController class Projects::TriggersController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project! before_action :authorize_admin_project!
layout 'project_settings' layout 'project_settings'
def index def index
@triggers = @ci_project.triggers @triggers = project.triggers
@trigger = Ci::Trigger.new @trigger = Ci::Trigger.new
end end
def create def create
@trigger = @ci_project.triggers.new @trigger = project.triggers.new
@trigger.save @trigger.save
if @trigger.valid? if @trigger.valid?
redirect_to namespace_project_triggers_path(@project.namespace, @project) redirect_to namespace_project_triggers_path(@project.namespace, @project)
else else
@triggers = @ci_project.triggers.select(&:persisted?) @triggers = project.triggers.select(&:persisted?)
render :index render :index
end end
end end
...@@ -30,6 +29,6 @@ class Projects::TriggersController < Projects::ApplicationController ...@@ -30,6 +29,6 @@ class Projects::TriggersController < Projects::ApplicationController
private private
def trigger def trigger
@trigger ||= @ci_project.triggers.find(params[:id]) @trigger ||= project.triggers.find(params[:id])
end end
end end
class Projects::VariablesController < Projects::ApplicationController class Projects::VariablesController < Projects::ApplicationController
before_action :ci_project
before_action :authorize_admin_project! before_action :authorize_admin_project!
layout 'project_settings' layout 'project_settings'
...@@ -8,9 +7,7 @@ class Projects::VariablesController < Projects::ApplicationController ...@@ -8,9 +7,7 @@ class Projects::VariablesController < Projects::ApplicationController
end end
def update def update
if ci_project.update_attributes(project_params) if project.update_attributes(project_params)
Ci::EventService.new.change_project_settings(current_user, ci_project)
redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.' redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.'
else else
render action: 'show' render action: 'show'
......
...@@ -210,10 +210,10 @@ class ProjectsController < ApplicationController ...@@ -210,10 +210,10 @@ class ProjectsController < ApplicationController
def project_params def project_params
params.require(:project).permit( params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list, :name, :path, :description, :issues_tracker, :tag_list, :runners_token,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:builds_enabled :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
) )
end end
......
module Ci
module GitlabHelper
def no_turbolink
{ :"data-no-turbolink" => "data-no-turbolink" }
end
def yaml_web_editor_link(project)
commits = project.commits
if commits.any? && commits.last.ci_yaml_file
"#{project.gitlab_url}/edit/master/.gitlab-ci.yml"
else
"#{project.gitlab_url}/new/master"
end
end
end
end
module Ci
module ProjectsHelper
def ref_tab_class ref = nil
'active' if ref == @ref
end
def success_ratio(success_builds, failed_builds)
failed_builds = failed_builds.count(:all)
success_builds = success_builds.count(:all)
return 100 if failed_builds.zero?
ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
ratio.to_i
end
def markdown_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
"[![build status](#{url})](#{ci_project_url(project, ref: ref)})"
end
def html_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
"<a href='#{ci_project_url(project, ref: ref)}'><img src='#{url}' /></a>"
end
def project_uses_specific_runner?(project)
project.runners.any?
end
def no_runners_for_project?(project)
project.runners.blank? &&
Ci::Runner.shared.blank?
end
end
end
module CiBadgeHelper
def markdown_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
link = namespace_project_commits_path(project.namespace, project, ref)
"[![build status](#{url})](#{link})"
end
def html_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
link = namespace_project_commits_path(project.namespace, project, ref)
"<a href='#{link}'><img src='#{url}' /></a>"
end
end
module CiStatusHelper module CiStatusHelper
def ci_status_path(ci_commit) def ci_status_path(ci_commit)
project = ci_commit.gl_project project = ci_commit.project
builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end end
...@@ -63,4 +63,9 @@ module CiStatusHelper ...@@ -63,4 +63,9 @@ module CiStatusHelper
ci_status_icon(ci_commit) ci_status_icon(ci_commit)
end end
end end
def no_runners_for_project?(project)
project.runners.blank? &&
Ci::Runner.shared.blank?
end
end end
...@@ -16,4 +16,14 @@ module GraphHelper ...@@ -16,4 +16,14 @@ module GraphHelper
ids = parents.map { |p| p.id } ids = parents.map { |p| p.id }
ids.zip(parent_spaces) ids.zip(parent_spaces)
end end
def success_ratio(success_builds, failed_builds)
failed_builds = failed_builds.count(:all)
success_builds = success_builds.count(:all)
return 100 if failed_builds.zero?
ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
ratio.to_i
end
end end
...@@ -19,7 +19,7 @@ module RunnersHelper ...@@ -19,7 +19,7 @@ module RunnersHelper
id = "\##{runner.id}" id = "\##{runner.id}"
if current_user && current_user.admin if current_user && current_user.admin
link_to ci_admin_runner_path(runner) do link_to admin_runner_path(runner) do
display_name + id display_name + id
end end
else else
......
module TriggersHelper module TriggersHelper
def ci_build_trigger_url(project_id, ref_name) def builds_trigger_url(project_id)
"#{Settings.gitlab_ci.url}/ci/api/v1/projects/#{project_id}/refs/#{ref_name}/trigger" "#{Settings.gitlab.url}/api/v3/projects/#{project_id}/trigger/builds"
end end
end end
module Ci
module Emails
module Builds
def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end
def build_success_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end
end
end
end
module Ci
class Notify < ActionMailer::Base
include Ci::Emails::Builds
add_template_helper Ci::GitlabHelper
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
default from: Gitlab.config.gitlab.email_from
# Just send email with 3 seconds delay
def self.delay
delay_for(2.seconds)
end
private
# Formats arguments into a String suitable for use as an email subject
#
# extra - Extra Strings to be inserted into the subject
#
# Examples
#
# >> subject('Lorem ipsum')
# => "GitLab-CI | Lorem ipsum"
#
# # Automatically inserts Project name when @project is set
# >> @project = Project.last
# => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# >> subject('Lorem ipsum')
# => "GitLab-CI | Ruby on Rails | Lorem ipsum "
#
# # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "GitLab-CI | Lorem ipsum | Dolor sit amet"
def subject(*extra)
subject = "GitLab-CI"
subject << (@project ? " | #{@project.name}" : "")
subject << " | " + extra.join(' | ') if extra.present?
subject
end
end
end
module Emails
module Builds
def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end
def build_success_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end
end
end
...@@ -7,6 +7,7 @@ class Notify < BaseMailer ...@@ -7,6 +7,7 @@ class Notify < BaseMailer
include Emails::Projects include Emails::Projects
include Emails::Profile include Emails::Profile
include Emails::Groups include Emails::Groups
include Emails::Builds
add_template_helper MergeRequestsHelper add_template_helper MergeRequestsHelper
add_template_helper EmailsHelper add_template_helper EmailsHelper
......
# == Schema Information
#
# Table name: ci_application_settings
#
# id :integer not null, primary key
# all_broken_builds :boolean
# add_pusher :boolean
# created_at :datetime
# updated_at :datetime
#
module Ci
class ApplicationSetting < ActiveRecord::Base
extend Ci::Model
CACHE_KEY = 'ci_application_setting.last'
after_commit do
Rails.cache.write(CACHE_KEY, self)
end
def self.expire
Rails.cache.delete(CACHE_KEY)
end
def self.current
Rails.cache.fetch(CACHE_KEY) do
Ci::ApplicationSetting.last
end
end
def self.create_from_defaults
create(
all_broken_builds: Settings.gitlab_ci['all_broken_builds'],
add_pusher: Settings.gitlab_ci['add_pusher'],
)
end
end
end
...@@ -84,6 +84,7 @@ module Ci ...@@ -84,6 +84,7 @@ module Ci
new_build.options = build.options new_build.options = build.options
new_build.commands = build.commands new_build.commands = build.commands
new_build.tag_list = build.tag_list new_build.tag_list = build.tag_list
new_build.gl_project_id = build.gl_project_id
new_build.commit_id = build.commit_id new_build.commit_id = build.commit_id
new_build.name = build.name new_build.name = build.name
new_build.allow_failure = build.allow_failure new_build.allow_failure = build.allow_failure
...@@ -96,21 +97,16 @@ module Ci ...@@ -96,21 +97,16 @@ module Ci
end end
state_machine :status, initial: :pending do state_machine :status, initial: :pending do
after_transition any => [:success, :failed, :canceled] do |build, transition| after_transition pending: :running do |build, transition|
return unless build.gl_project build.execute_hooks
end
project = build.project
if project.web_hooks? after_transition any => [:success, :failed, :canceled] do |build, transition|
Ci::WebHookService.new.build_end(build) return unless build.project
end
build.update_coverage
build.commit.create_next_builds(build) build.commit.create_next_builds(build)
project.execute_services(build) build.execute_hooks
if project.coverage_enabled?
build.update_coverage
end
end end
end end
...@@ -119,7 +115,7 @@ module Ci ...@@ -119,7 +115,7 @@ module Ci
end end
def retryable? def retryable?
commands.present? project.builds_enabled? && commands.present?
end end
def retried? def retried?
...@@ -132,7 +128,7 @@ module Ci ...@@ -132,7 +128,7 @@ module Ci
end end
def timeout def timeout
project.timeout project.build_timeout
end end
def variables def variables
...@@ -151,26 +147,21 @@ module Ci ...@@ -151,26 +147,21 @@ module Ci
project.name project.name
end end
def project_recipients
recipients = project.email_recipients.split(' ')
if project.email_add_pusher? && user.present? && user.notification_email.present?
recipients << user.notification_email
end
recipients.uniq
end
def repo_url def repo_url
project.repo_url_with_auth auth = "gitlab-ci-token:#{token}@"
project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
prefix + auth
end
end end
def allow_git_fetch def allow_git_fetch
project.allow_git_fetch project.build_allow_git_fetch
end end
def update_coverage def update_coverage
coverage = extract_coverage(trace, project.coverage_regex) coverage_regex = project.build_coverage_regex
return unless coverage_regex
coverage = extract_coverage(trace, coverage_regex)
if coverage.is_a? Numeric if coverage.is_a? Numeric
update_attributes(coverage: coverage) update_attributes(coverage: coverage)
...@@ -203,7 +194,7 @@ module Ci ...@@ -203,7 +194,7 @@ module Ci
def trace def trace
trace = raw_trace trace = raw_trace
if project && trace.present? if project && trace.present?
trace.gsub(project.token, 'xxxxxx') trace.gsub(project.runners_token, 'xxxxxx')
else else
trace trace
end end
...@@ -230,29 +221,29 @@ module Ci ...@@ -230,29 +221,29 @@ module Ci
end end
def token def token
project.token project.runners_token
end end
def valid_token? token def valid_token? token
project.valid_token? token project.valid_runners_token? token
end end
def target_url def target_url
Gitlab::Application.routes.url_helpers. Gitlab::Application.routes.url_helpers.
namespace_project_build_url(gl_project.namespace, gl_project, self) namespace_project_build_url(project.namespace, project, self)
end end
def cancel_url def cancel_url
if active? if active?
Gitlab::Application.routes.url_helpers. Gitlab::Application.routes.url_helpers.
cancel_namespace_project_build_path(gl_project.namespace, gl_project, self) cancel_namespace_project_build_path(project.namespace, project, self)
end end
end end
def retry_url def retry_url
if retryable? if retryable?
Gitlab::Application.routes.url_helpers. Gitlab::Application.routes.url_helpers.
retry_namespace_project_build_path(gl_project.namespace, gl_project, self) retry_namespace_project_build_path(project.namespace, project, self)
end end
end end
...@@ -271,10 +262,18 @@ module Ci ...@@ -271,10 +262,18 @@ module Ci
def download_url def download_url
if artifacts_file.exists? if artifacts_file.exists?
Gitlab::Application.routes.url_helpers. Gitlab::Application.routes.url_helpers.
download_namespace_project_build_path(gl_project.namespace, gl_project, self) download_namespace_project_build_path(project.namespace, project, self)
end end
end end
def execute_hooks
build_data = Gitlab::BuildDataBuilder.build(self)
project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks)
end
private private
def yaml_variables def yaml_variables
......
...@@ -20,8 +20,8 @@ module Ci ...@@ -20,8 +20,8 @@ module Ci
class Commit < ActiveRecord::Base class Commit < ActiveRecord::Base
extend Ci::Model extend Ci::Model
belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :statuses, dependent: :destroy, class_name: 'CommitStatus' has_many :statuses, class_name: 'CommitStatus'
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
...@@ -38,10 +38,6 @@ module Ci ...@@ -38,10 +38,6 @@ module Ci
sha sha
end end
def project
@project ||= gl_project.ensure_gitlab_ci_project
end
def project_id def project_id
project.id project.id
end end
...@@ -57,7 +53,7 @@ module Ci ...@@ -57,7 +53,7 @@ module Ci
end end
def valid_commit_sha def valid_commit_sha
if self.sha == Ci::Git::BLANK_SHA if self.sha == Gitlab::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)") self.errors.add(:sha, " cant be 00000000 (branch removal)")
end end
end end
...@@ -79,7 +75,7 @@ module Ci ...@@ -79,7 +75,7 @@ module Ci
end end
def commit_data def commit_data
@commit ||= gl_project.commit(sha) @commit ||= project.commit(sha)
rescue rescue
nil nil
end end
...@@ -178,16 +174,18 @@ module Ci ...@@ -178,16 +174,18 @@ module Ci
duration_array.reduce(:+).to_i duration_array.reduce(:+).to_i
end end
def started_at
@started_at ||= statuses.order('started_at ASC').first.try(:started_at)
end
def finished_at def finished_at
@finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at) @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
end end
def coverage def coverage
if project.coverage_enabled? coverage_array = latest_builds.map(&:coverage).compact
coverage_array = latest_builds.map(&:coverage).compact if coverage_array.size >= 1
if coverage_array.size >= 1 '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
end end
end end
...@@ -197,7 +195,7 @@ module Ci ...@@ -197,7 +195,7 @@ module Ci
def config_processor def config_processor
return nil unless ci_yaml_file return nil unless ci_yaml_file
@config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, gl_project.path_with_namespace) @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
save_yaml_error(e.message) save_yaml_error(e.message)
nil nil
...@@ -207,7 +205,7 @@ module Ci ...@@ -207,7 +205,7 @@ module Ci
end end
def ci_yaml_file def ci_yaml_file
@ci_yaml_file ||= gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data @ci_yaml_file ||= project.repository.blob_at(sha, '.gitlab-ci.yml').data
rescue rescue
nil nil
end end
...@@ -227,7 +225,7 @@ module Ci ...@@ -227,7 +225,7 @@ module Ci
# on project level and `.gitlab-ci.yml` file is present. # on project level and `.gitlab-ci.yml` file is present.
# #
def show_build_status? def show_build_status?
gl_project.builds_enabled? && ci_yaml_file project.builds_enabled? && ci_yaml_file
end end
private private
......
# == Schema Information
#
# Table name: ci_events
#
# id :integer not null, primary key
# project_id :integer
# user_id :integer
# is_admin :integer
# description :text
# created_at :datetime
# updated_at :datetime
#
module Ci
class Event < ActiveRecord::Base
extend Ci::Model
belongs_to :project, class_name: 'Ci::Project'
validates :description,
presence: true,
length: { in: 5..200 }
scope :admin, ->(){ where(is_admin: true) }
scope :project_wide, ->(){ where(is_admin: false) }
end
end
# == Schema Information
#
# Table name: ci_projects
#
# id :integer not null, primary key
# name :string(255)
# timeout :integer default(3600), not null
# created_at :datetime
# updated_at :datetime
# token :string(255)
# default_ref :string(255)
# path :string(255)
# always_build :boolean default(FALSE), not null
# polling_interval :integer
# public :boolean default(FALSE), not null
# ssh_url_to_repo :string(255)
# gitlab_id :integer
# allow_git_fetch :boolean default(TRUE), not null
# email_recipients :string(255) default(""), not null
# email_add_pusher :boolean default(TRUE), not null
# email_only_broken_builds :boolean default(TRUE), not null
# skip_refs :string(255)
# coverage_regex :string(255)
# shared_runners_enabled :boolean default(FALSE)
# generated_yaml_config :text
#
module Ci
class Project < ActiveRecord::Base
extend Ci::Model
include Ci::ProjectStatus
belongs_to :gl_project, class_name: '::Project', foreign_key: :gitlab_id
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, class_name: 'Ci::Runner'
has_many :web_hooks, dependent: :destroy, class_name: 'Ci::WebHook'
has_many :events, dependent: :destroy, class_name: 'Ci::Event'
has_many :variables, dependent: :destroy, class_name: 'Ci::Variable'
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger'
# Project services
has_many :services, dependent: :destroy, class_name: 'Ci::Service'
has_one :hip_chat_service, dependent: :destroy, class_name: 'Ci::HipChatService'
has_one :slack_service, dependent: :destroy, class_name: 'Ci::SlackService'
has_one :mail_service, dependent: :destroy, class_name: 'Ci::MailService'
accepts_nested_attributes_for :variables, allow_destroy: true
delegate :name_with_namespace, :path_with_namespace, :web_url, :http_url_to_repo, :ssh_url_to_repo, to: :gl_project
#
# Validations
#
validates_presence_of :timeout, :token, :default_ref, :gitlab_id
validates_uniqueness_of :gitlab_id
validates :polling_interval,
presence: true,
if: ->(project) { project.always_build.present? }
before_validation :set_default_values
class << self
include Ci::CurrentSettings
def unassigned(runner)
joins("LEFT JOIN #{Ci::RunnerProject.table_name} ON #{Ci::RunnerProject.table_name}.project_id = #{Ci::Project.table_name}.id " \
"AND #{Ci::RunnerProject.table_name}.runner_id = #{runner.id}").
where("#{Ci::RunnerProject.table_name}.project_id" => nil)
end
def ordered_by_last_commit_date
last_commit_subquery = "(SELECT gl_project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY gl_project_id)"
joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.gitlab_id = last_commit.gl_project_id").
joins(:gl_project).
order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC")
end
end
def name
name_with_namespace
end
def path
path_with_namespace
end
def gitlab_url
web_url
end
def any_runners?(&block)
if runners.active.any?(&block)
return true
end
shared_runners_enabled && Ci::Runner.shared.active.any?(&block)
end
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
self.default_ref ||= 'master'
end
def tracked_refs
@tracked_refs ||= default_ref.split(",").map { |ref| ref.strip }
end
def valid_token? token
self.token && self.token == token
end
def no_running_builds?
# Get running builds not later than 3 days ago to ignore hangs
builds.running.where("updated_at > ?", 3.days.ago).empty?
end
def email_notification?
email_add_pusher || email_recipients.present?
end
def web_hooks?
web_hooks.any?
end
def services?
services.any?
end
def timeout_in_minutes
timeout / 60
end
def timeout_in_minutes=(value)
self.timeout = value.to_i * 60
end
def coverage_enabled?
coverage_regex.present?
end
# Build a clone-able repo url
# using http and basic auth
def repo_url_with_auth
auth = "gitlab-ci-token:#{token}@"
http_url_to_repo.sub(/^https?:\/\//) do |prefix|
prefix + auth
end
end
def available_services_names
%w(slack mail hip_chat)
end
def build_missing_services
available_services_names.each do |service_name|
service = services.find { |service| service.to_param == service_name }
# If service is available but missing in db
# we should create an instance. Ex `create_gitlab_ci_service`
self.send :"create_#{service_name}_service" if service.nil?
end
end
def execute_services(data)
services.each do |service|
# Call service hook only if it is active
begin
service.execute(data) if service.active && service.can_execute?(data)
rescue => e
logger.error(e)
end
end
end
def setup_finished?
commits.any?
end
def commits
gl_project.ci_commits.ordered
end
def builds
gl_project.ci_builds
end
end
end
module Ci
module ProjectStatus
def status
last_commit.status if last_commit
end
def broken?
last_commit.failed? if last_commit
end
def success?
last_commit.success? if last_commit
end
def broken_or_success?
broken? || success?
end
def last_commit
@last_commit ||= commits.last if commits.any?
end
def last_commit_date
last_commit.try(:created_at)
end
def human_status
status
end
end
end
...@@ -25,7 +25,7 @@ module Ci ...@@ -25,7 +25,7 @@ module Ci
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
has_many :projects, through: :runner_projects, class_name: 'Ci::Project' has_many :projects, through: :runner_projects, class_name: '::Project', foreign_key: :gl_project_id
has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build' has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
...@@ -45,10 +45,6 @@ module Ci ...@@ -45,10 +45,6 @@ module Ci
query: "%#{query.try(:downcase)}%") query: "%#{query.try(:downcase)}%")
end end
def gl_projects_ids
projects.select(:gitlab_id)
end
def set_default_values def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank? self.token = SecureRandom.hex(15) if self.token.blank?
end end
......
...@@ -14,8 +14,8 @@ module Ci ...@@ -14,8 +14,8 @@ module Ci
extend Ci::Model extend Ci::Model
belongs_to :runner, class_name: 'Ci::Runner' belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :project, class_name: 'Ci::Project' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
validates_uniqueness_of :runner_id, scope: :project_id validates_uniqueness_of :runner_id, scope: :gl_project_id
end end
end end
# == Schema Information
#
# Table name: ci_services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
# To add new service you should build a class inherited from Service
# and implement a set of methods
module Ci
class Service < ActiveRecord::Base
extend Ci::Model
serialize :properties, JSON
default_value_for :active, false
after_initialize :initialize_properties
belongs_to :project, class_name: 'Ci::Project'
validates :project_id, presence: true
def activated?
active
end
def category
:common
end
def initialize_properties
self.properties = {} if properties.nil?
end
def title
# implement inside child
end
def description
# implement inside child
end
def help
# implement inside child
end
def to_param
# implement inside child
end
def fields
# implement inside child
[]
end
def can_test?
project.builds.any?
end
def can_execute?(build)
true
end
def execute(build)
# implement inside child
end
# Provide convenient accessor methods
# for each serialized property.
def self.prop_accessor(*args)
args.each do |arg|
class_eval %{
def #{arg}
(properties || {})['#{arg}']
end
def #{arg}=(value)
self.properties ||= {}
self.properties['#{arg}'] = value
end
}
end
end
def self.boolean_accessor(*args)
self.prop_accessor(*args)
args.each do |arg|
class_eval %{
def #{arg}?
ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
end
}
end
end
end
end
...@@ -16,7 +16,7 @@ module Ci ...@@ -16,7 +16,7 @@ module Ci
acts_as_paranoid acts_as_paranoid
belongs_to :project, class_name: 'Ci::Project' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
validates_presence_of :token validates_presence_of :token
......
...@@ -15,10 +15,10 @@ module Ci ...@@ -15,10 +15,10 @@ module Ci
class Variable < ActiveRecord::Base class Variable < ActiveRecord::Base
extend Ci::Model extend Ci::Model
belongs_to :project, class_name: 'Ci::Project' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
validates_presence_of :key validates_presence_of :key
validates_uniqueness_of :key, scope: :project_id validates_uniqueness_of :key, scope: :gl_project_id
attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base
end end
......
# == Schema Information
#
# Table name: ci_web_hooks
#
# id :integer not null, primary key
# url :string(255) not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
#
module Ci
class WebHook < ActiveRecord::Base
extend Ci::Model
include HTTParty
belongs_to :project, class_name: 'Ci::Project'
# HTTParty timeout
default_timeout 10
validates :url, presence: true, url: true
def execute(data)
parsed_url = URI.parse(url)
if parsed_url.userinfo.blank?
Ci::WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = {
username: URI.decode(parsed_url.user),
password: URI.decode(parsed_url.password),
}
Ci::WebHook.post(post_url,
body: data.to_json,
headers: { "Content-Type" => "application/json" },
verify: false,
basic_auth: auth)
end
end
end
end
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
class CommitStatus < ActiveRecord::Base class CommitStatus < ActiveRecord::Base
self.table_name = 'ci_builds' self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
belongs_to :commit, class_name: 'Ci::Commit' belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :user belongs_to :user
...@@ -76,7 +77,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -76,7 +77,7 @@ class CommitStatus < ActiveRecord::Base
end end
after_transition [:pending, :running] => :success do |build, transition| after_transition [:pending, :running] => :success do |build, transition|
MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.gl_project, nil).trigger(build) MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.project, nil).trigger(build)
end end
state :pending, value: 'pending' state :pending, value: 'pending'
...@@ -86,8 +87,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -86,8 +87,7 @@ class CommitStatus < ActiveRecord::Base
state :canceled, value: 'canceled' state :canceled, value: 'canceled'
end end
delegate :sha, :short_sha, :gl_project, delegate :sha, :short_sha, to: :commit, prefix: false
to: :commit, prefix: false
# TODO: this should be removed with all references # TODO: this should be removed with all references
def before_sha def before_sha
......
...@@ -25,4 +25,5 @@ class ProjectHook < WebHook ...@@ -25,4 +25,5 @@ class ProjectHook < WebHook
scope :issue_hooks, -> { where(issues_events: true) } scope :issue_hooks, -> { where(issues_events: true) }
scope :note_hooks, -> { where(note_events: true) } scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true) }
scope :build_hooks, -> { where(build_events: true) }
end end
...@@ -26,6 +26,7 @@ class WebHook < ActiveRecord::Base ...@@ -26,6 +26,7 @@ class WebHook < ActiveRecord::Base
default_value_for :note_events, false default_value_for :note_events, false
default_value_for :merge_requests_events, false default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false default_value_for :tag_push_events, false
default_value_for :build_events, false
default_value_for :enable_ssl_verification, true default_value_for :enable_ssl_verification, true
# HTTParty timeout # HTTParty timeout
......
...@@ -56,6 +56,7 @@ class Project < ActiveRecord::Base ...@@ -56,6 +56,7 @@ class Project < ActiveRecord::Base
default_value_for :wiki_enabled, gitlab_config_features.wiki default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :wall_enabled, false default_value_for :wall_enabled, false
default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
# set last_activity_at to the same as created_at # set last_activity_at to the same as created_at
after_create :set_last_activity_at after_create :set_last_activity_at
...@@ -77,10 +78,10 @@ class Project < ActiveRecord::Base ...@@ -77,10 +78,10 @@ class Project < ActiveRecord::Base
# Project services # Project services
has_many :services has_many :services
has_one :gitlab_ci_service, dependent: :destroy
has_one :campfire_service, dependent: :destroy has_one :campfire_service, dependent: :destroy
has_one :drone_ci_service, dependent: :destroy has_one :drone_ci_service, dependent: :destroy
has_one :emails_on_push_service, dependent: :destroy has_one :emails_on_push_service, dependent: :destroy
has_one :builds_email_service, dependent: :destroy
has_one :irker_service, dependent: :destroy has_one :irker_service, dependent: :destroy
has_one :pivotaltracker_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy
has_one :hipchat_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy
...@@ -121,14 +122,21 @@ class Project < ActiveRecord::Base ...@@ -121,14 +122,21 @@ class Project < ActiveRecord::Base
has_many :deploy_keys, through: :deploy_keys_projects has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user has_many :starrers, through: :users_star_projects, source: :user
has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build'
has_many :releases, dependent: :destroy has_many :releases, dependent: :destroy
has_many :lfs_objects_projects, dependent: :destroy has_many :lfs_objects_projects, dependent: :destroy
has_many :lfs_objects, through: :lfs_objects_projects has_many :lfs_objects, through: :lfs_objects_projects
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id
has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id
has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
has_many :builds, class_name: 'Ci::Build', foreign_key: :gl_project_id # the builds are created from the commit_statuses
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject', foreign_key: :gl_project_id
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id
accepts_nested_attributes_for :variables, allow_destroy: true
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true delegate :members, to: :team, prefix: true
...@@ -161,6 +169,11 @@ class Project < ActiveRecord::Base ...@@ -161,6 +169,11 @@ class Project < ActiveRecord::Base
if: ->(project) { project.avatar.present? && project.avatar_changed? } if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :set_runners_token_token
def set_runners_token_token
self.runners_token = SecureRandom.hex(15) if self.runners_token.blank?
end
mount_uploader :avatar, AvatarUploader mount_uploader :avatar, AvatarUploader
# Scopes # Scopes
...@@ -256,6 +269,10 @@ class Project < ActiveRecord::Base ...@@ -256,6 +269,10 @@ class Project < ActiveRecord::Base
projects.iwhere('projects.path' => project_path).take projects.iwhere('projects.path' => project_path).take
end end
def find_by_ci_id(id)
find_by(ci_id: id.to_i)
end
def visibility_levels def visibility_levels
Gitlab::VisibilityLevel.options Gitlab::VisibilityLevel.options
end end
...@@ -790,28 +807,6 @@ class Project < ActiveRecord::Base ...@@ -790,28 +807,6 @@ class Project < ActiveRecord::Base
ci_commit(sha) || ci_commits.create(sha: sha) ci_commit(sha) || ci_commits.create(sha: sha)
end end
def ensure_gitlab_ci_project
gitlab_ci_project || create_gitlab_ci_project(
shared_runners_enabled: current_application_settings.shared_runners_enabled
)
end
# TODO: this should be migrated to Project table,
# the same as issues_enabled
def builds_enabled
gitlab_ci_service && gitlab_ci_service.active
end
def builds_enabled?
builds_enabled
end
def builds_enabled=(value)
service = gitlab_ci_service || create_gitlab_ci_service
service.active = value
service.save
end
def enable_ci def enable_ci
self.builds_enabled = true self.builds_enabled = true
end end
...@@ -825,4 +820,34 @@ class Project < ActiveRecord::Base ...@@ -825,4 +820,34 @@ class Project < ActiveRecord::Base
forked_project_link.destroy forked_project_link.destroy
end end
end end
def any_runners?(&block)
if runners.active.any?(&block)
return true
end
shared_runners_enabled? && Ci::Runner.shared.active.any?(&block)
end
def valid_runners_token? token
self.runners_token && self.runners_token == token
end
# TODO (ayufan): For now we use runners_token (backward compatibility)
# In 8.4 every build will have its own individual token valid for time of build
def valid_build_token? token
self.builds_enabled? && self.runners_token && self.runners_token == token
end
def build_coverage_enabled?
build_coverage_regex.present?
end
def build_timeout_in_minutes
build_timeout / 60
end
def build_timeout_in_minutes=(value)
self.build_timeout = value.to_i * 60
end
end end
...@@ -18,40 +18,73 @@ ...@@ -18,40 +18,73 @@
# note_events :boolean default(TRUE), not null # note_events :boolean default(TRUE), not null
# #
require 'spec_helper' class BuildsEmailService < Service
prop_accessor :recipients
describe GitlabCiService, models: true do boolean_accessor :add_pusher
describe 'associations' do boolean_accessor :notify_only_broken_builds
it { is_expected.to belong_to(:project) } validates :recipients, presence: true, if: :activated?
it { is_expected.to have_one(:service_hook) }
end def initialize_properties
if properties.nil?
describe 'commits methods' do self.properties = {}
before do self.notify_only_broken_builds = true
@ci_project = create(:ci_project)
@service = GitlabCiService.new
allow(@service).to receive_messages(
service_hook: true,
project_url: 'http://ci.gitlab.org/projects/2',
token: 'verySecret',
project: @ci_project.gl_project
)
end end
end
def title
'Builds emails'
end
describe :build_page do def description
it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://localhost/#{@ci_project.gl_project.path_with_namespace}/commit/2ab7834c/builds")} 'Email the builds status to a list of recipients.'
end
def to_param
'builds_email'
end
def supported_events
%w(build)
end
def execute(push_data)
return unless supported_events.include?(push_data[:object_kind])
if should_build_be_notified?(push_data)
BuildEmailWorker.perform_async(
push_data[:build_id],
all_recipients(push_data),
push_data,
)
end end
end
describe "execute" do def fields
let(:user) { create(:user, username: 'username') } [
let(:project) { create(:project, name: 'project') } { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by comma' },
let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } { type: 'checkbox', name: 'add_pusher', label: 'Add pusher to recipients list' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
]
end
it "calls CreateCommitService" do def should_build_be_notified?(data)
expect_any_instance_of(Ci::CreateCommitService).to receive(:execute).with(@ci_project, user, push_sample_data) case data[:build_status]
when 'success'
!notify_only_broken_builds?
when 'failed'
true
else
false
end
end
@service.execute(push_sample_data) def all_recipients(data)
end all_recipients = recipients.split(',')
if add_pusher? && data[:user][:email]
all_recipients << "#{data[:user][:email]}"
end end
all_recipients
end end
end end
module Ci
class HipChatMessage
include Gitlab::Application.routes.url_helpers
attr_reader :build
def initialize(build)
@build = build
end
def to_s
lines = Array.new
lines.push("<a href=\"#{ci_project_url(project)}\">#{project.name}</a> - ")
lines.push("<a href=\"#{builds_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}\">Commit ##{commit.id}</a></br>")
lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>")
lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).")
lines.join('')
end
def status_color(build_or_commit=nil)
build_or_commit ||= commit_status
case build_or_commit
when :success
'green'
when :failed, :canceled
'red'
else # :pending, :running or unknown
'yellow'
end
end
def notify?
[:failed, :canceled].include?(commit_status)
end
private
def commit
build.commit
end
def project
commit.project
end
def build_status
build.status.to_sym
end
def commit_status
commit.status.to_sym
end
def humanized_status(build_or_commit=nil)
build_or_commit ||= commit_status
case build_or_commit
when :pending
"Pending"
when :running
"Running"
when :failed
"Failed"
when :success
"Successful"
when :canceled
"Canceled"
else
"Unknown"
end
end
end
end
# == Schema Information
#
# Table name: ci_services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
module Ci
class HipChatService < Ci::Service
prop_accessor :hipchat_token, :hipchat_room, :hipchat_server
boolean_accessor :notify_only_broken_builds
validates :hipchat_token, presence: true, if: :activated?
validates :hipchat_room, presence: true, if: :activated?
default_value_for :notify_only_broken_builds, true
def title
"HipChat"
end
def description
"Private group chat, video chat, instant messaging for teams"
end
def help
end
def to_param
'hip_chat'
end
def fields
[
{ type: 'text', name: 'hipchat_token', label: 'Token', placeholder: '' },
{ type: 'text', name: 'hipchat_room', label: 'Room', placeholder: '' },
{ type: 'text', name: 'hipchat_server', label: 'Server', placeholder: 'https://hipchat.example.com', help: 'Leave blank for default' },
{ type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
]
end
def can_execute?(build)
return if build.allow_failure?
commit = build.commit
return unless commit
return unless commit.latest_builds.include? build
case commit.status.to_sym
when :failed
true
when :success
true unless notify_only_broken_builds?
else
false
end
end
def execute(build)
msg = Ci::HipChatMessage.new(build)
opts = default_options.merge(
token: hipchat_token,
room: hipchat_room,
server: server_url,
color: msg.status_color,
notify: msg.notify?
)
Ci::HipChatNotifierWorker.perform_async(msg.to_s, opts)
end
private
def default_options
{
service_name: 'GitLab CI',
message_format: 'html'
}
end
def server_url
if hipchat_server.blank?
'https://api.hipchat.com'
else
hipchat_server
end
end
end
end
# == Schema Information
#
# Table name: ci_services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
module Ci
class MailService < Ci::Service
delegate :email_recipients, :email_recipients=,
:email_add_pusher, :email_add_pusher=,
:email_only_broken_builds, :email_only_broken_builds=, to: :project, prefix: false
before_save :update_project
default_value_for :active, true
def title
'Mail'
end
def description
'Email notification'
end
def to_param
'mail'
end
def fields
[
{ type: 'text', name: 'email_recipients', label: 'Recipients', help: 'Whitespace-separated list of recipient addresses' },
{ type: 'checkbox', name: 'email_add_pusher', label: 'Add pusher to recipients list' },
{ type: 'checkbox', name: 'email_only_broken_builds', label: 'Notify only broken builds' }
]
end
def can_execute?(build)
return if build.allow_failure?
# it doesn't make sense to send emails for retried builds
commit = build.commit
return unless commit
return unless commit.latest_builds.include?(build)
case build.status.to_sym
when :failed
true
when :success
true unless email_only_broken_builds
else
false
end
end
def execute(build)
build.project_recipients.each do |recipient|
case build.status.to_sym
when :success
mailer.build_success_email(build.id, recipient).deliver_later
when :failed
mailer.build_fail_email(build.id, recipient).deliver_later
end
end
end
private
def update_project
project.save!
end
def mailer
Ci::Notify
end
end
end
require 'slack-notifier'
module Ci
class SlackMessage
include Gitlab::Application.routes.url_helpers
def initialize(commit)
@commit = commit
end
def pretext
''
end
def color
attachment_color
end
def fallback
format(attachment_message)
end
def attachments
fields = []
commit.latest_builds.each do |build|
next if build.allow_failure?
next unless build.failed?
fields << {
title: build.name,
value: "Build <#{namespace_project_build_url(build.gl_project.namespace, build.gl_project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)."
}
end
[{
text: attachment_message,
color: attachment_color,
fields: fields
}]
end
private
attr_reader :commit
def attachment_message
out = "<#{ci_project_url(project)}|#{project_name}>: "
out << "Commit <#{builds_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}|\##{commit.id}> "
out << "(<#{commit_sha_link}|#{commit.short_sha}>) "
out << "of <#{commit_ref_link}|#{commit.ref}> "
out << "by #{commit.git_author_name} " if commit.git_author_name
out << "#{commit_status} in "
out << "#{commit.duration} second(s)"
end
def format(string)
Slack::Notifier::LinkFormatter.format(string)
end
def project
commit.project
end
def project_name
project.name
end
def commit_sha_link
"#{project.gitlab_url}/commit/#{commit.sha}"
end
def commit_ref_link
"#{project.gitlab_url}/commits/#{commit.ref}"
end
def attachment_color
if commit.success?
'good'
else
'danger'
end
end
def commit_status
if commit.success?
'succeeded'
else
'failed'
end
end
end
end
# == Schema Information
#
# Table name: ci_services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
module Ci
class SlackService < Ci::Service
prop_accessor :webhook
boolean_accessor :notify_only_broken_builds
validates :webhook, presence: true, if: :activated?
default_value_for :notify_only_broken_builds, true
def title
'Slack'
end
def description
'A team communication tool for the 21st century'
end
def to_param
'slack'
end
def help
'Visit https://www.slack.com/services/new/incoming-webhook. Then copy link and save project!' unless webhook.present?
end
def fields
[
{ type: 'text', name: 'webhook', label: 'Webhook URL', placeholder: '' },
{ type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
]
end
def can_execute?(build)
return if build.allow_failure?
commit = build.commit
return unless commit
return unless commit.latest_builds.include?(build)
case commit.status.to_sym
when :failed
true
when :success
true unless notify_only_broken_builds?
else
false
end
end
def execute(build)
message = Ci::SlackMessage.new(build.commit)
options = default_options.merge(
color: message.color,
fallback: message.fallback,
attachments: message.attachments
)
Ci::SlackNotifierWorker.perform_async(webhook, message.pretext, options)
end
private
def default_options
{
username: 'GitLab CI'
}
end
end
end
...@@ -19,76 +19,5 @@ ...@@ -19,76 +19,5 @@
# #
class GitlabCiService < CiService class GitlabCiService < CiService
include Gitlab::Application.routes.url_helpers # this is no longer used
after_save :compose_service_hook, if: :activated?
after_save :ensure_gitlab_ci_project, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def ensure_gitlab_ci_project
return unless project
project.ensure_gitlab_ci_project
end
def supported_events
%w(push tag_push)
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
ci_project = project.gitlab_ci_project
if ci_project
current_user = User.find_by(id: data[:user_id])
Ci::CreateCommitService.new.execute(ci_project, current_user, data)
end
end
def token
if project.gitlab_ci_project.present?
project.gitlab_ci_project.token
end
end
def get_ci_commit(sha, ref)
Ci::Project.find(project.gitlab_ci_project.id).commits.find_by_sha!(sha)
end
def commit_status(sha, ref)
get_ci_commit(sha, ref).status
rescue ActiveRecord::RecordNotFound
:error
end
def commit_coverage(sha, ref)
get_ci_commit(sha, ref).coverage
rescue ActiveRecord::RecordNotFound
:error
end
def build_page(sha, ref)
if project.gitlab_ci_project.present?
builds_namespace_project_commit_url(project.namespace, project, sha)
end
end
def title
'GitLab CI'
end
def description
'Continuous integration server from GitLab'
end
def to_param
'gitlab_ci'
end
def fields
[]
end
end end
...@@ -22,8 +22,16 @@ class HipchatService < Service ...@@ -22,8 +22,16 @@ class HipchatService < Service
MAX_COMMITS = 3 MAX_COMMITS = 3
prop_accessor :token, :room, :server, :notify, :color, :api_version prop_accessor :token, :room, :server, :notify, :color, :api_version
boolean_accessor :notify_only_broken_builds
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def initialize_properties
if properties.nil?
self.properties = {}
self.notify_only_broken_builds = true
end
end
def title def title
'HipChat' 'HipChat'
end end
...@@ -45,12 +53,13 @@ class HipchatService < Service ...@@ -45,12 +53,13 @@ class HipchatService < Service
{ type: 'text', name: 'api_version', { type: 'text', name: 'api_version',
placeholder: 'Leave blank for default (v2)' }, placeholder: 'Leave blank for default (v2)' },
{ type: 'text', name: 'server', { type: 'text', name: 'server',
placeholder: 'Leave blank for default. https://hipchat.example.com' } placeholder: 'Leave blank for default. https://hipchat.example.com' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
] ]
end end
def supported_events def supported_events
%w(push issue merge_request note tag_push) %w(push issue merge_request note tag_push build)
end end
def execute(data) def execute(data)
...@@ -94,6 +103,8 @@ class HipchatService < Service ...@@ -94,6 +103,8 @@ class HipchatService < Service
create_merge_request_message(data) unless is_update?(data) create_merge_request_message(data) unless is_update?(data)
when "note" when "note"
create_note_message(data) create_note_message(data)
when "build"
create_build_message(data) if should_build_be_notified?(data)
end end
end end
...@@ -235,6 +246,20 @@ class HipchatService < Service ...@@ -235,6 +246,20 @@ class HipchatService < Service
message message
end end
def create_build_message(data)
ref_type = data[:tag] ? 'tag' : 'branch'
ref = data[:ref]
sha = data[:sha]
user_name = data[:commit][:author_name]
status = data[:commit][:status]
duration = data[:commit][:duration]
branch_link = "<a href=\"#{project_url}/commits/#{URI.escape(ref)}\">#{ref}</a>"
commit_link = "<a href=\"#{project_url}/commit/#{URI.escape(sha)}/builds\">#{Commit.truncate_sha(sha)}</a>"
"#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
end
def project_name def project_name
project.name_with_namespace.gsub(/\s/, '') project.name_with_namespace.gsub(/\s/, '')
end end
...@@ -250,4 +275,24 @@ class HipchatService < Service ...@@ -250,4 +275,24 @@ class HipchatService < Service
def is_update?(data) def is_update?(data)
data[:object_attributes][:action] == 'update' data[:object_attributes][:action] == 'update'
end end
def humanized_status(status)
case status
when 'success'
'passed'
else
status
end
end
def should_build_be_notified?(data)
case data[:commit][:status]
when 'success'
!notify_only_broken_builds?
when 'failed'
true
else
false
end
end
end end
...@@ -20,8 +20,16 @@ ...@@ -20,8 +20,16 @@
class SlackService < Service class SlackService < Service
prop_accessor :webhook, :username, :channel prop_accessor :webhook, :username, :channel
boolean_accessor :notify_only_broken_builds
validates :webhook, presence: true, if: :activated? validates :webhook, presence: true, if: :activated?
def initialize_properties
if properties.nil?
self.properties = {}
self.notify_only_broken_builds = true
end
end
def title def title
'Slack' 'Slack'
end end
...@@ -45,12 +53,13 @@ class SlackService < Service ...@@ -45,12 +53,13 @@ class SlackService < Service
{ type: 'text', name: 'webhook', { type: 'text', name: 'webhook',
placeholder: 'https://hooks.slack.com/services/...' }, placeholder: 'https://hooks.slack.com/services/...' },
{ type: 'text', name: 'username', placeholder: 'username' }, { type: 'text', name: 'username', placeholder: 'username' },
{ type: 'text', name: 'channel', placeholder: '#channel' } { type: 'text', name: 'channel', placeholder: '#channel' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
] ]
end end
def supported_events def supported_events
%w(push issue merge_request note tag_push) %w(push issue merge_request note tag_push build)
end end
def execute(data) def execute(data)
...@@ -78,6 +87,8 @@ class SlackService < Service ...@@ -78,6 +87,8 @@ class SlackService < Service
MergeMessage.new(data) unless is_update?(data) MergeMessage.new(data) unless is_update?(data)
when "note" when "note"
NoteMessage.new(data) NoteMessage.new(data)
when "build"
BuildMessage.new(data) if should_build_be_notified?(data)
end end
opt = {} opt = {}
...@@ -86,7 +97,7 @@ class SlackService < Service ...@@ -86,7 +97,7 @@ class SlackService < Service
if message if message
notifier = Slack::Notifier.new(webhook, opt) notifier = Slack::Notifier.new(webhook, opt)
notifier.ping(message.pretext, attachments: message.attachments) notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback)
end end
end end
...@@ -103,9 +114,21 @@ class SlackService < Service ...@@ -103,9 +114,21 @@ class SlackService < Service
def is_update?(data) def is_update?(data)
data[:object_attributes][:action] == 'update' data[:object_attributes][:action] == 'update'
end end
def should_build_be_notified?(data)
case data[:commit][:status]
when 'success'
!notify_only_broken_builds?
when 'failed'
true
else
false
end
end
end end
require "slack_service/issue_message" require "slack_service/issue_message"
require "slack_service/push_message" require "slack_service/push_message"
require "slack_service/merge_message" require "slack_service/merge_message"
require "slack_service/note_message" require "slack_service/note_message"
require "slack_service/build_message"
...@@ -10,6 +10,9 @@ class SlackService ...@@ -10,6 +10,9 @@ class SlackService
format(message) format(message)
end end
def fallback
end
def attachments def attachments
raise NotImplementedError raise NotImplementedError
end end
......
class SlackService
class BuildMessage < BaseMessage
attr_reader :sha
attr_reader :ref_type
attr_reader :ref
attr_reader :status
attr_reader :project_name
attr_reader :project_url
attr_reader :user_name
attr_reader :duration
def initialize(params, commit = true)
@sha = params[:sha]
@ref_type = params[:tag] ? 'tag' : 'branch'
@ref = params[:ref]
@project_name = params[:project_name]
@project_url = params[:project_url]
@status = params[:commit][:status]
@user_name = params[:commit][:author_name]
@duration = params[:commit][:duration]
end
def pretext
''
end
def fallback
format(message)
end
def attachments
[{ text: format(message), color: attachment_color }]
end
private
def message
"#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} second(s)"
end
def format(string)
Slack::Notifier::LinkFormatter.format(string)
end
def humanized_status
case status
when 'success'
'passed'
else
status
end
end
def attachment_color
if status == 'success'
'good'
else
'danger'
end
end
def branch_url
"#{project_url}/commits/#{ref}"
end
def branch_link
"[#{ref}](#{branch_url})"
end
def project_link
"[#{project_name}](#{project_url})"
end
def commit_url
"#{project_url}/commit/#{sha}/builds"
end
def commit_link
"[#{Commit.truncate_sha(sha)}](#{commit_url})"
end
end
end
...@@ -30,6 +30,7 @@ class Service < ActiveRecord::Base ...@@ -30,6 +30,7 @@ class Service < ActiveRecord::Base
default_value_for :merge_requests_events, true default_value_for :merge_requests_events, true
default_value_for :tag_push_events, true default_value_for :tag_push_events, true
default_value_for :note_events, true default_value_for :note_events, true
default_value_for :build_events, true
after_initialize :initialize_properties after_initialize :initialize_properties
...@@ -40,13 +41,14 @@ class Service < ActiveRecord::Base ...@@ -40,13 +41,14 @@ class Service < ActiveRecord::Base
validates :project_id, presence: true, unless: Proc.new { |service| service.template? } validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') } scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) }
scope :push_hooks, -> { where(push_events: true, active: true) } scope :push_hooks, -> { where(push_events: true, active: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
scope :issue_hooks, -> { where(issues_events: true, active: true) } scope :issue_hooks, -> { where(issues_events: true, active: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
scope :note_hooks, -> { where(note_events: true, active: true) } scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) }
def activated? def activated?
active active
...@@ -133,6 +135,21 @@ class Service < ActiveRecord::Base ...@@ -133,6 +135,21 @@ class Service < ActiveRecord::Base
end end
end end
# Provide convenient boolean accessor methods
# for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.boolean_accessor(*args)
self.prop_accessor(*args)
args.each do |arg|
class_eval %{
def #{arg}?
ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
end
}
end
end
# Returns a hash of the properties that have been assigned a new value since last save, # Returns a hash of the properties that have been assigned a new value since last save,
# indicating their original values (attr => original value). # indicating their original values (attr => original value).
# ActiveRecord does not provide a mechanism to track changes in serialized keys, # ActiveRecord does not provide a mechanism to track changes in serialized keys,
...@@ -163,6 +180,7 @@ class Service < ActiveRecord::Base ...@@ -163,6 +180,7 @@ class Service < ActiveRecord::Base
assembla assembla
bamboo bamboo
buildkite buildkite
builds_email
campfire campfire
custom_issue_tracker custom_issue_tracker
drone_ci drone_ci
...@@ -170,7 +188,6 @@ class Service < ActiveRecord::Base ...@@ -170,7 +188,6 @@ class Service < ActiveRecord::Base
external_wiki external_wiki
flowdock flowdock
gemnasium gemnasium
gitlab_ci
hipchat hipchat
irker irker
jira jira
......
...@@ -134,7 +134,7 @@ class User < ActiveRecord::Base ...@@ -134,7 +134,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
has_one :abuse_report, dependent: :destroy has_one :abuse_report, dependent: :destroy
has_many :ci_builds, dependent: :nullify, class_name: 'Ci::Build' has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
# #
...@@ -769,10 +769,9 @@ class User < ActiveRecord::Base ...@@ -769,10 +769,9 @@ class User < ActiveRecord::Base
def ci_authorized_runners def ci_authorized_runners
@ci_authorized_runners ||= begin @ci_authorized_runners ||= begin
runner_ids = Ci::RunnerProject.joins(:project). runner_ids = Ci::RunnerProject.
where("ci_projects.gitlab_id IN (#{ci_projects_union.to_sql})"). where("ci_runner_projects.gl_project_id IN (#{ci_projects_union.to_sql})").
select(:runner_id) select(:runner_id)
Ci::Runner.specific.where(id: runner_ids) Ci::Runner.specific.where(id: runner_ids)
end end
end end
......
...@@ -29,9 +29,11 @@ module Ci ...@@ -29,9 +29,11 @@ module Ci
build_attrs.merge!(ref: ref, build_attrs.merge!(ref: ref,
tag: tag, tag: tag,
trigger_request: trigger_request, trigger_request: trigger_request,
user: user) user: user,
project: commit.project)
commit.builds.create!(build_attrs) build = commit.builds.create!(build_attrs)
build.execute_hooks
end end
end end
end end
......
module Ci
class CreateCommitService
def execute(project, user, params)
sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref]
unless origin_ref && sha.present?
return false
end
ref = origin_ref.gsub(/\Arefs\/(tags|heads)\//, '')
# Skip branch removal
if sha == Ci::Git::BLANK_SHA
return false
end
tag = origin_ref.start_with?('refs/tags/')
commit = project.gl_project.ensure_ci_commit(sha)
unless commit.skip_ci?
commit.update_committed!
commit.create_builds(ref, tag, user)
end
commit
end
end
end
module Ci module Ci
class CreateTriggerRequestService class CreateTriggerRequestService
def execute(project, trigger, ref, variables = nil) def execute(project, trigger, ref, variables = nil)
commit = project.gl_project.commit(ref) commit = project.commit(ref)
return unless commit return unless commit
# check if ref is tag # check if ref is tag
tag = project.gl_project.repository.find_tag(ref).present? tag = project.repository.find_tag(ref).present?
ci_commit = project.gl_project.ensure_ci_commit(commit.sha) ci_commit = project.ensure_ci_commit(commit.sha)
trigger_request = trigger.trigger_requests.create!( trigger_request = trigger.trigger_requests.create!(
variables: variables, variables: variables,
......
module Ci
class EventService
def remove_project(user, project)
create(
description: "Project \"#{project.name}\" has been removed by #{user.username}",
user_id: user.id,
is_admin: true
)
end
def create_project(user, project)
create(
description: "Project \"#{project.name}\" has been created by #{user.username}",
user_id: user.id,
is_admin: true
)
end
def change_project_settings(user, project)
create(
project_id: project.id,
user_id: user.id,
description: "User \"#{user.username}\" updated projects settings"
)
end
def create(*args)
Ci::Event.create!(*args)
end
end
end
...@@ -4,10 +4,10 @@ module Ci ...@@ -4,10 +4,10 @@ module Ci
sha = params[:sha] sha = params[:sha]
sha ||= sha ||=
if params[:ref] if params[:ref]
project.gl_project.commit(params[:ref]).try(:sha) project.commit(params[:ref]).try(:sha)
end end
commit = project.commits.ordered.find_by(sha: sha) commit = project.ci_commits.ordered.find_by(sha: sha)
image_name = image_for_commit(commit) image_name = image_for_commit(commit)
image_path = Rails.root.join('public/ci', image_name) image_path = Rails.root.join('public/ci', image_name)
......
...@@ -8,10 +8,10 @@ module Ci ...@@ -8,10 +8,10 @@ module Ci
builds = builds =
if current_runner.shared? if current_runner.shared?
# don't run projects which have not enables shared runners # don't run projects which have not enables shared runners
builds.joins(commit: { gl_project: :gitlab_ci_project }).where(ci_projects: { shared_runners_enabled: true }) builds.joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true })
else else
# do run projects which are only assigned to this runner # do run projects which are only assigned to this runner
builds.joins(:commit).where(ci_commits: { gl_project_id: current_runner.gl_projects_ids }) builds.where(project: current_runner.projects.where(builds_enabled: true))
end end
builds = builds.order('created_at ASC') builds = builds.order('created_at ASC')
...@@ -20,10 +20,9 @@ module Ci ...@@ -20,10 +20,9 @@ module Ci
build.can_be_served?(current_runner) build.can_be_served?(current_runner)
end end
if build if build
# In case when 2 runners try to assign the same build, second runner will be declined # In case when 2 runners try to assign the same build, second runner will be declined
# with StateMachine::InvalidTransition in run! method. # with StateMachines::InvalidTransition in run! method.
build.with_lock do build.with_lock do
build.runner_id = current_runner.id build.runner_id = current_runner.id
build.save! build.save!
...@@ -33,7 +32,7 @@ module Ci ...@@ -33,7 +32,7 @@ module Ci
build build
rescue StateMachine::InvalidTransition rescue StateMachines::InvalidTransition
nil nil
end end
end end
......
module Ci
class TestHookService
def execute(hook, current_user)
Ci::WebHookService.new.build_end(hook.project.commits.last.last_build)
end
end
end
class CreateCommitBuildsService
def execute(project, user, params)
return false unless project.builds_enabled?
sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref]
unless origin_ref && sha.present?
return false
end
ref = Gitlab::Git.ref_name(origin_ref)
# Skip branch removal
if sha == Gitlab::Git::BLANK_SHA
return false
end
tag = Gitlab::Git.tag_ref?(origin_ref)
commit = project.ensure_ci_commit(sha)
unless commit.skip_ci?
commit.update_committed!
commit.create_builds(ref, tag, user)
end
commit
end
end
...@@ -61,6 +61,7 @@ class GitPushService ...@@ -61,6 +61,7 @@ class GitPushService
EventCreateService.new.push(project, user, @push_data) EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks) project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks) project.execute_services(@push_data.dup, :push_hooks)
CreateCommitBuildsService.new.execute(project, @user, @push_data)
ProjectCacheWorker.perform_async(project.id) ProjectCacheWorker.perform_async(project.id)
end end
......
...@@ -10,6 +10,7 @@ class GitTagPushService ...@@ -10,6 +10,7 @@ class GitTagPushService
EventCreateService.new.push(project, user, @push_data) EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :tag_push_hooks) project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks) project.execute_services(@push_data.dup, :tag_push_hooks)
CreateCommitBuildsService.new.execute(project, @user, @push_data)
ProjectCacheWorker.perform_async(project.id) ProjectCacheWorker.perform_async(project.id)
true true
......
...@@ -7,6 +7,8 @@ module Projects ...@@ -7,6 +7,8 @@ module Projects
description: @project.description, description: @project.description,
name: @project.name, name: @project.name,
path: @project.path, path: @project.path,
shared_runners_enabled: @project.shared_runners_enabled,
builds_enabled: @project.builds_enabled,
namespace_id: @params[:namespace].try(:id) || current_user.namespace.id namespace_id: @params[:namespace].try(:id) || current_user.namespace.id
} }
...@@ -15,19 +17,6 @@ module Projects ...@@ -15,19 +17,6 @@ module Projects
end end
new_project = CreateService.new(current_user, new_params).execute new_project = CreateService.new(current_user, new_params).execute
if new_project.persisted?
if @project.builds_enabled?
new_project.enable_ci
settings = @project.gitlab_ci_project.attributes.select do |attr_name, value|
["public", "shared_runners_enabled", "allow_git_fetch"].include? attr_name
end
new_project.gitlab_ci_project.update(settings)
end
end
new_project new_project
end end
end end
......
- project = build.project
%tr.build
%td.status
= ci_status_with_icon(build.status)
%td.build-link
- if build.target_url
= link_to build.target_url do
%strong Build ##{build.id}
- else
%strong Build ##{build.id}
- if build.show_warning?
%i.fa.fa-warning.text-warning
%td
- if project
= link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project), class: "monospace"
%td
= link_to build.short_sha, namespace_project_commit_path(project.namespace, project, build.sha), class: "monospace"
%td
- if build.ref
= link_to build.ref, namespace_project_commits_path(project.namespace, project, build.ref)
- else
.light none
%td
- if build.try(:runner)
= runner_link(build.runner)
- else
.light none
%td
#{build.stage} / #{build.name}
.pull-right
- if build.tags.any?
- build.tags.each do |tag|
%span.label.label-primary
= tag
- if build.try(:trigger_request)
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
%td.duration
- if build.duration
#{duration_in_words(build.finished_at, build.started_at)}
%td.timestamp
- if build.finished_at
%span #{time_ago_with_tooltip(build.finished_at)}
- if defined?(coverage) && coverage
%td.coverage
- if build.try(:coverage)
#{build.coverage}%
%td
.pull-right
- if current_user && can?(current_user, :download_build_artifacts, project) && build.download_url
= link_to build.download_url, title: 'Download artifacts' do
%i.fa.fa-download
- if current_user && can?(current_user, :manage_builds, build.project)
- if build.active?
- if build.cancel_url
= link_to build.cancel_url, method: :post, title: 'Cancel' do
%i.fa.fa-remove.cred
- elsif defined?(allow_retry) && allow_retry && build.retry_url
= link_to build.retry_url, method: :post, title: 'Retry' do
%i.fa.fa-repeat
.project-issuable-filter
.controls
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
%ul.center-top-menu
%li{class: ('active' if @scope.nil?)}
= link_to admin_builds_path do
Running
%span.badge.js-running-count= @all_builds.running_or_pending.count(:id)
%li{class: ('active' if @scope == 'finished')}
= link_to admin_builds_path(scope: :finished) do
Finished
%span.badge.js-running-count= @all_builds.finished.count(:id)
%li{class: ('active' if @scope == 'all')}
= link_to admin_builds_path(scope: :all) do
All
%span.badge.js-totalbuilds-count= @all_builds.count(:id)
.gray-content-block
#{(@scope || 'running').capitalize} builds
%ul.content-list
- if @builds.blank?
%li
.nothing-here-block No builds to show
- else
.table-holder
%table.table.builds
%thead
%tr
%th Status
%th Build ID
%th Project
%th Commit
%th Ref
%th Runner
%th Name
%th Duration
%th Finished at
%th
- @builds.each do |build|
= render "admin/builds/build", build: build
= paginate @builds, theme: 'gitlab'
...@@ -8,14 +8,14 @@ ...@@ -8,14 +8,14 @@
%span.label.label-danger paused %span.label.label-danger paused
%td %td
= link_to ci_admin_runner_path(runner) do = link_to admin_runner_path(runner) do
= runner.short_sha = runner.short_sha
%td %td
.runner-description .runner-description
= runner.description = runner.description
%span (#{link_to 'edit', '#', class: 'edit-runner-link'}) %span (#{link_to 'edit', '#', class: 'edit-runner-link'})
.runner-description-form.hide .runner-description-form.hide
= form_for [:ci, :admin, runner], remote: true, html: { class: 'form-inline' } do |f| = form_for [:admin, runner], remote: true, html: { class: 'form-inline' } do |f|
.form-group .form-group
= f.text_field :description, class: 'form-control' = f.text_field :description, class: 'form-control'
= f.submit 'Save', class: 'btn' = f.submit 'Save', class: 'btn'
...@@ -38,11 +38,11 @@ ...@@ -38,11 +38,11 @@
Never Never
%td %td
.pull-right .pull-right
= link_to 'Edit', ci_admin_runner_path(runner), class: 'btn btn-sm' = link_to 'Edit', admin_runner_path(runner), class: 'btn btn-sm'
&nbsp; &nbsp;
- if runner.active? - if runner.active?
= link_to 'Pause', [:pause, :ci, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm' = link_to 'Pause', [:pause, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm'
- else - else
= link_to 'Resume', [:resume, :ci, :admin, runner], method: :get, class: 'btn btn-success btn-sm' = link_to 'Resume', [:resume, :admin, runner], method: :get, class: 'btn btn-success btn-sm'
= link_to 'Remove', [:ci, :admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' = link_to 'Remove', [:admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
.append-bottom-20.clearfix .append-bottom-20.clearfix
.pull-left .pull-left
= form_tag ci_admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do = form_tag admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
.form-group .form-group
= search_field_tag :search, params[:search], class: 'form-control', placeholder: 'Runner description or token', spellcheck: false = search_field_tag :search, params[:search], class: 'form-control', placeholder: 'Runner description or token', spellcheck: false
= submit_tag 'Search', class: 'btn' = submit_tag 'Search', class: 'btn'
...@@ -49,5 +49,5 @@ ...@@ -49,5 +49,5 @@
%th %th
- @runners.each do |runner| - @runners.each do |runner|
= render "ci/admin/runners/runner", runner: runner = render "admin/runners/runner", runner: runner
= paginate @runners = paginate @runners
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
%h4 This runner will process builds only from ASSIGNED projects %h4 This runner will process builds only from ASSIGNED projects
%p You can't make this a shared runner. %p You can't make this a shared runner.
%hr %hr
= form_for @runner, url: ci_admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f| = form_for @runner, url: admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f|
.form-group .form-group
= label_tag :token, class: 'control-label' do = label_tag :token, class: 'control-label' do
Token Token
...@@ -53,29 +53,24 @@ ...@@ -53,29 +53,24 @@
%th %th
- @runner.runner_projects.each do |runner_project| - @runner.runner_projects.each do |runner_project|
- project = runner_project.project - project = runner_project.project
- if project.gl_project - if project
%tr.alert-info %tr.alert-info
%td %td
%strong %strong
= project.name = project.name_with_namespace
%td %td
.pull-right .pull-right
= link_to 'Disable', [:ci, :admin, project, runner_project], method: :delete, class: 'btn btn-danger btn-xs' = link_to 'Disable', [:admin, project.namespace, project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
%table.table %table.table
%thead %thead
%tr %tr
%th Project %th Project
%th %th
.pull-right
= link_to 'Assign to all', assign_all_ci_admin_runner_path(@runner),
class: 'btn btn-sm assign-all-runner',
title: 'Assign runner to all projects',
method: :put
%tr %tr
%td %td
= form_tag ci_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do = form_tag admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
.form-group .form-group
= search_field_tag :search, params[:search], class: 'form-control', spellcheck: false = search_field_tag :search, params[:search], class: 'form-control', spellcheck: false
= submit_tag 'Search', class: 'btn' = submit_tag 'Search', class: 'btn'
...@@ -84,44 +79,44 @@ ...@@ -84,44 +79,44 @@
- @projects.each do |project| - @projects.each do |project|
%tr %tr
%td %td
= project.name = project.name_with_namespace
%td %td
.pull-right .pull-right
= form_for [:ci, :admin, project, project.runner_projects.new] do |f| = form_for [:admin, project.namespace.becomes(Namespace), project, project.runner_projects.new] do |f|
= f.hidden_field :runner_id, value: @runner.id = f.hidden_field :runner_id, value: @runner.id
= f.submit 'Enable', class: 'btn btn-xs' = f.submit 'Enable', class: 'btn btn-xs'
= paginate @projects = paginate @projects
.col-md-6 .col-md-6
%h4 Recent builds served by this runner %h4 Recent builds served by this runner
%table.builds.runner-builds %table.table.builds.runner-builds
%thead %thead
%tr %tr
%th Build ID %th Build
%th Status %th Status
%th Project %th Project
%th Commit %th Commit
%th Finished at %th Finished at
- @builds.each do |build| - @builds.each do |build|
- gl_project = build.gl_project - project = build.project
%tr.build %tr.build
%td.id %td.id
- if gl_project - if project
= link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do = link_to namespace_project_build_path(project.namespace, project, build) do
= build.id %strong ##{build.id}
- else - else
= build.id %strong ##{build.id}
%td.status %td.status
= ci_status_with_icon(build.status) = ci_status_with_icon(build.status)
%td.status %td.status
- if gl_project - if project
= gl_project.name_with_namespace = project.name_with_namespace
%td.build-link %td.build-link
- if gl_project - if project
= link_to ci_status_path(build.commit) do = link_to ci_status_path(build.commit) do
%strong #{build.commit.short_sha} %strong #{build.commit.short_sha}
......
= form_for @application_setting, url: ci_admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
- if @application_setting.errors.any?
#error_explanation
.alert.alert-danger
- @application_setting.errors.full_messages.each do |msg|
%p= msg
%fieldset
%legend Default Project Settings
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :all_broken_builds do
= f.check_box :all_broken_builds
Send emails only on broken builds
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :add_pusher do
= f.check_box :add_pusher
Add pusher to recipients list
.form-actions
= f.submit 'Save', class: 'btn btn-primary'
%h3.page-title Settings
%hr
= render 'form'
- gl_project = build.project.gl_project
- if build.commit && build.project
%tr.build
%td.build-link
= link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
%strong #{build.id}
%td.status
= ci_status_with_icon(build.status)
%td.commit-link
= link_to ci_status_path(build.commit) do
%strong #{build.commit.short_sha}
%td.runner
- if build.runner
= link_to build.runner.id, ci_admin_runner_path(build.runner)
%td.build-project
= truncate build.project.name, length: 30
%td.build-message
%span= truncate(build.commit.git_commit_message, length: 30)
%td.build-branch
%span= truncate(build.ref, length: 25)
%td.duration
- if build.duration
#{duration_in_words(build.finished_at, build.started_at)}
%td.timestamp
- if build.finished_at
%span #{time_ago_in_words build.finished_at} ago
%ul.nav.nav-tabs.append-bottom-20
%li{class: ("active" if @scope.nil?)}
= link_to 'All builds', ci_admin_builds_path
%li{class: ("active" if @scope == "pending")}
= link_to "Pending", ci_admin_builds_path(scope: :pending)
%li{class: ("active" if @scope == "running")}
= link_to "Running", ci_admin_builds_path(scope: :running)
%table.builds
%thead
%tr
%th Build
%th Status
%th Commit
%th Runner
%th Project
%th Message
%th Branch
%th Duration
%th Finished at
- @builds.each do |build|
= render "ci/admin/builds/build", build: build
= paginate @builds
.table-holder
%table.table
%thead
%tr
%th User ID
%th Description
%th When
- @events.each do |event|
%tr
%td
= event.user_id
%td
= event.description
%td.light
= time_ago_in_words event.updated_at
ago
= paginate @events
- last_commit = project.commits.last
%tr
%td
= project.id
%td
= link_to [:ci, project] do
%strong= project.name
%td
- if last_commit
= ci_status_with_icon(last_commit.status)
- if project.last_commit_date
&middot;
= time_ago_in_words project.last_commit_date
ago
- else
No builds yet
%td
- if project.public
%i.fa.fa-globe
Public
- else
%i.fa.fa-lock
Private
%td
= project.commits.count
%td
= link_to [:ci, :admin, project], method: :delete, class: 'btn btn-danger btn-sm' do
%i.fa.fa-remove
Remove
.table-holder
%table.table
%thead
%tr
%th ID
%th Name
%th Last build
%th Access
%th Builds
%th
- @projects.each do |project|
= render "ci/admin/projects/project", project: project
= paginate @projects
%p.lead
To register a new runner visit #{link_to 'this page ', ci_runners_path}
.row
.col-md-8
%h5 Activated:
%table.table
%tr
%th Runner ID
%th Runner Description
%th Last build
%th Builds Stats
%th Registered
%th
- @runner_projects.each do |runner_project|
- runner = runner_project.runner
- builds = runner.builds.where(project_id: @project.id)
%tr
%td
%span.badge.badge-info= runner.id
%td
= runner.display_name
%td
- last_build = builds.last
- if last_build
= link_to last_build.short_sha, [last_build.project, last_build]
- else
unknown
%td
%span.badge.badge-success
#{builds.success.count}
%span /
%span.badge.badge-important
#{builds.failed.count}
%td
#{time_ago_in_words(runner_project.created_at)} ago
%td
= link_to 'Disable', [:ci, @project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm right'
.col-md-4
%h5 Available
%table.table
%tr
%th ID
%th Token
%th
- (Ci::Runner.all - @project.runners).each do |runner|
%tr
%td
= runner.id
%td
= runner.token
%td
= form_for [:ci, @project, @runner_project] do |f|
= f.hidden_field :runner_id, value: runner.id
= f.submit 'Add', class: 'btn btn-sm'
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
- if commit.finished_at - if commit.finished_at
%span #{time_ago_in_words commit.finished_at} ago %span #{time_ago_in_words commit.finished_at} ago
- if commit.project.coverage_enabled? - if commit.coverage
%td.coverage %td.coverage
- if commit.coverage #{commit.coverage}%
#{commit.coverage}%
...@@ -4,12 +4,10 @@ ...@@ -4,12 +4,10 @@
%ol %ol
%li %li
Add at least one runner to the project. Add at least one runner to the project.
Go to #{link_to 'Runners page', runners_path(@project.gl_project), target: :blank} for instructions. Go to #{link_to 'Runners page', runners_path(@project), target: :blank} for instructions.
%li %li
Put the .gitlab-ci.yml in the root of your repository. Examples can be found in #{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}. Put the .gitlab-ci.yml in the root of your repository. Examples can be found in
#{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}.
You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
%li
Visit #{link_to 'GitLab project settings', @project.gitlab_url + "/services/gitlab_ci/edit", target: :blank}
and press the "Test settings" button.
%li %li
Return to this page and refresh it, it should show a new build. Return to this page and refresh it, it should show a new build.
.login-block
%h2 Login using GitLab account
%p.light
Make sure you have an account on the GitLab server
= link_to GitlabCi.config.gitlab_server.url, GitlabCi.config.gitlab_server.url, no_turbolink
%hr
= link_to "Login with GitLab", auth_ci_user_sessions_path(state: params[:state]), no_turbolink.merge( class: 'btn btn-login btn-success' )
%ul.nav.nav-sidebar
= nav_link do
= link_to admin_root_path, title: 'Back to admin', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Back to admin
%li.separate-item
= nav_link path: 'projects#index' do
= link_to ci_admin_projects_path do
= icon('list-alt fw')
%span
Projects
= nav_link path: 'events#index' do
= link_to ci_admin_events_path do
= icon('book fw')
%span
Events
= nav_link path: ['runners#index', 'runners#show'] do
= link_to ci_admin_runners_path do
= icon('cog fw')
%span
Runners
%span.count= Ci::Runner.count(:all)
= nav_link path: 'builds#index' do
= link_to ci_admin_builds_path do
= icon('link fw')
%span
Builds
%span.count= Ci::Build.count(:all)
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to ci_admin_application_settings_path do
= icon('cogs fw')
%span
Settings
%ul.nav.nav-sidebar
= nav_link do
= link_to project_path(@project.gl_project), title: 'Back to project', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Back to project
%li.separate-item
= nav_link path: 'events#index' do
= link_to ci_project_events_path(@project) do
= icon('book fw')
%span
Events
!!! 5
%html{ lang: "en"}
= render 'layouts/head'
%body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page}
- header_title = "Admin area"
- if current_user
= render "layouts/header/default", title: header_title
- else
= render "layouts/header/public", title: header_title
= render 'layouts/ci/page', sidebar: 'nav_admin'
!!! 5
%html{ lang: "en"}
= render 'layouts/head'
%body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page}
- header_title = "Continuous Integration"
- if current_user
= render "layouts/header/default", title: header_title
- else
= render "layouts/header/public", title: header_title
= render 'layouts/ci/page'
...@@ -24,11 +24,18 @@ ...@@ -24,11 +24,18 @@
= icon('key fw') = icon('key fw')
%span %span
Deploy Keys Deploy Keys
= nav_link do = nav_link path: ['runners#index', 'runners#show'] do
= link_to ci_admin_projects_path, title: 'Continuous Integration' do = link_to admin_runners_path do
= icon('building fw') = icon('cog fw')
%span
Runners
%span.count= Ci::Runner.count(:all)
= nav_link path: 'builds#index' do
= link_to admin_builds_path do
= icon('link fw')
%span %span
Continuous Integration Builds
%span.count= Ci::Build.count(:all)
= nav_link(controller: :logs) do = nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs' do = link_to admin_logs_path, title: 'Logs' do
= icon('file-text fw') = icon('file-text fw')
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
= icon('cubes fw') = icon('cubes fw')
%span %span
Builds Builds
%span.count.builds_counter= @project.ci_builds.running_or_pending.count(:all) %span.count.builds_counter= @project.builds.running_or_pending.count(:all)
- if project_nav_tab? :graphs - if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do = nav_link(controller: %w(graphs)) do
......
...@@ -50,18 +50,3 @@ ...@@ -50,18 +50,3 @@
= icon('retweet fw') = icon('retweet fw')
%span %span
Triggers Triggers
= nav_link path: 'ci_web_hooks#index' do
= link_to namespace_project_ci_web_hooks_path(@project.namespace, @project), title: 'CI Web Hooks' do
= icon('link fw')
%span
CI Web Hooks
= nav_link path: 'ci_settings#edit' do
= link_to edit_namespace_project_ci_settings_path(@project.namespace, @project), title: 'CI Settings' do
= icon('building fw')
%span
CI Settings
= nav_link controller: 'ci_services' do
= link_to namespace_project_ci_services_path(@project.namespace, @project), title: 'CI Services' do
= icon('share fw')
%span
CI Services
- content_for :header do - content_for :header do
%h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"} %h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
GitLab CI (build failed) GitLab (build failed)
%h3 %h3
Project: Project:
= link_to ci_project_url(@project) do = link_to ci_project_url(@project) do
= @project.name = @project.name
%p %p
Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.gl_project.namespace, @build.gl_project, @build.sha)} Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
%p %p
Author: #{@build.commit.git_author_name} Author: #{@build.commit.git_author_name}
%p %p
...@@ -20,4 +20,4 @@ ...@@ -20,4 +20,4 @@
Message: #{@build.commit.git_commit_message} Message: #{@build.commit.git_commit_message}
%p %p
Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)} Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
...@@ -8,4 +8,4 @@ Stage: <%= @build.stage %> ...@@ -8,4 +8,4 @@ Stage: <%= @build.stage %>
Job: <%= @build.name %> Job: <%= @build.name %>
Message: <%= @build.commit.git_commit_message %> Message: <%= @build.commit.git_commit_message %>
Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %> Url: <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
- content_for :header do - content_for :header do
%h1{style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"} %h1{style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
GitLab CI (build successful) GitLab (build successful)
%h3 %h3
Project: Project:
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= @project.name = @project.name
%p %p
Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.gl_project.namespace, @build.gl_project, @build.sha)} Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.project.namespace, @build.project, @build.sha)}
%p %p
Author: #{@build.commit.git_author_name} Author: #{@build.commit.git_author_name}
%p %p
...@@ -21,4 +21,4 @@ ...@@ -21,4 +21,4 @@
Message: #{@build.commit.git_commit_message} Message: #{@build.commit.git_commit_message}
%p %p
Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)} Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
...@@ -8,4 +8,4 @@ Stage: <%= @build.stage %> ...@@ -8,4 +8,4 @@ Stage: <%= @build.stage %>
Job: <%= @build.name %> Job: <%= @build.name %>
Message: <%= @build.commit.git_commit_message %> Message: <%= @build.commit.git_commit_message %>
Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %> Url: <%= namespace_project_build_url(@build.project.namespace, @build.project, @build) %>
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.project-issuable-filter .project-issuable-filter
.controls .controls
- if @ci_project && can?(current_user, :manage_builds, @project) - if can?(current_user, :manage_builds, @project)
.pull-left.hidden-xs .pull-left.hidden-xs
- if @all_builds.running_or_pending.any? - if @all_builds.running_or_pending.any?
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
%thead %thead
%tr %tr
%th Status %th Status
%th Build ID %th Runner
%th Commit %th Commit
%th Ref %th Ref
%th Stage %th Stage
......
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
%br %br
Go to Go to
= link_to namespace_project_runners_path(@build.gl_project.namespace, @build.gl_project) do = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
Runners page Runners page
.row.prepend-top-default .row.prepend-top-default
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
%p %p
%span.attr-name Runner: %span.attr-name Runner:
- if @build.runner && current_user && current_user.admin - if @build.runner && current_user && current_user.admin
= link_to "##{@build.runner.id}", ci_admin_runner_path(@build.runner.id) = link_to "##{@build.runner.id}", admin_runner_path(@build.runner.id)
- elsif @build.runner - elsif @build.runner
\##{@build.runner.id} \##{@build.runner.id}
......
%h3.page-title
= @service.title
= boolean_to_icon @service.activated?
%p= @service.description
%hr
= form_for(@service, as: :service, url: namespace_project_ci_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
- if @service.errors.any?
.alert.alert-danger
%ul
- @service.errors.full_messages.each do |msg|
%li= msg
- if @service.help.present?
.bs-callout
= @service.help
.form-group
= f.label :active, "Active", class: "control-label"
.col-sm-10
= f.check_box :active
- @service.fields.each do |field|
- name = field[:name]
- label = field[:label] || name
- value = @service.send(name)
- type = field[:type]
- placeholder = field[:placeholder]
- choices = field[:choices]
- default_choice = field[:default_choice]
- help = field[:help]
.form-group
= f.label label, class: "control-label"
.col-sm-10
- if type == 'text'
= f.text_field name, class: "form-control", placeholder: placeholder
- elsif type == 'textarea'
= f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
- elsif type == 'checkbox'
= f.check_box name
- elsif type == 'select'
= f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- if help
.light #{help}
.form-actions
= f.submit 'Save', class: 'btn btn-save'
&nbsp;
- if @service.valid? && @service.activated? && @service.can_test?
= link_to 'Test settings', test_namespace_project_ci_service_path(@project.namespace, @project, @service.to_param), class: 'btn'
- page_title @service.title, "CI Services"
= render 'form'
- page_title "CI Services"
%h3.page-title Project services
%p.light Project services allow you to integrate GitLab CI with other applications
%table.table
%thead
%tr
%th
%th Service
%th Description
%th Last edit
- @services.sort_by(&:title).each do |service|
%tr
%td
= boolean_to_icon service.activated?
%td
= link_to edit_namespace_project_ci_service_path(@project.namespace, @project, service.to_param) do
%strong= service.title
%td
= service.description
%td.light
= time_ago_in_words service.updated_at
ago
%h3.page-title
CI settings
%hr
.bs-callout.help-callout
%p
If you want to test your .gitlab-ci.yml, you can use special tool - #{link_to "Lint", ci_lint_path}
%p
Edit your
#{link_to ".gitlab-ci.yml using web-editor", yaml_web_editor_link(@ci_project)}
- unless @project.empty_repo?
%p
Paste build status image for #{@repository.root_ref} with next link
= link_to '#', class: 'badge-codes-toggle btn btn-default btn-xs' do
Status Badge
.badge-codes-block.bs-callout.bs-callout-info.hide
%p
Status badge for
%span.label.label-info #{@ref}
branch
%div
%label Markdown:
= text_field_tag 'badge_md', markdown_badge_code(@ci_project, @repository.root_ref), readonly: true, class: 'form-control'
%label Html:
= text_field_tag 'badge_html', html_badge_code(@ci_project, @repository.root_ref), readonly: true, class: 'form-control'
= nested_form_for @ci_project, url: namespace_project_ci_settings_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
- if @ci_project.errors.any?
#error_explanation
%p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:"
.alert.alert-error
%ul
- @ci_project.errors.full_messages.each do |msg|
%li= msg
%fieldset
%legend Build settings
.form-group
= label_tag nil, class: 'control-label' do
Get code
.col-sm-10
%p Get recent application code using the following command:
.radio
= label_tag do
= f.radio_button :allow_git_fetch, 'false'
%strong git clone
.light Slower but makes sure you have a clean dir before every build
.radio
= label_tag do
= f.radio_button :allow_git_fetch, 'true'
%strong git fetch
.light Faster
.form-group
= f.label :timeout_in_minutes, 'Timeout', class: 'control-label'
.col-sm-10
= f.number_field :timeout_in_minutes, class: 'form-control', min: '0'
.light per build in minutes
%fieldset
%legend Build Schedule
.form-group
= f.label :always_build, 'Schedule build', class: 'control-label'
.col-sm-10
.checkbox
= f.label :always_build do
= f.check_box :always_build
%span.light Repeat last build after X hours if no builds
.form-group
= f.label :polling_interval, "Build interval", class: 'control-label'
.col-sm-10
= f.number_field :polling_interval, placeholder: '5', min: '0', class: 'form-control'
.light In hours
%fieldset
%legend Project settings
.form-group
= f.label :default_ref, "Make tabs for the following branches", class: 'control-label'
.col-sm-10
= f.text_field :default_ref, class: 'form-control', placeholder: 'master, stable'
.light You will be able to filter builds by the following branches
.form-group
= f.label :public, 'Public mode', class: 'control-label'
.col-sm-10
.checkbox
= f.label :public do
= f.check_box :public
%span.light Anyone can see project and builds
.form-group
= f.label :coverage_regex, "Test coverage parsing", class: 'control-label'
.col-sm-10
.input-group
%span.input-group-addon /
= f.text_field :coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
%span.input-group-addon /
.light We will use this regular expression to find test coverage output in build trace. Leave blank if you want to disable this feature
.bs-callout.bs-callout-info
%p Below are examples of regex for existing tools:
%ul
%li
Simplecov (Ruby) -
%code \(\d+.\d+\%\) covered
%li
pytest-cov (Python) -
%code \d+\%\s*$
%li
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
%fieldset
%legend Advanced settings
.form-group
= f.label :token, "CI token", class: 'control-label'
.col-sm-10
= f.text_field :token, class: 'form-control', placeholder: 'xEeFCaDAB89'
.form-actions
= f.submit 'Save changes', class: 'btn btn-save'
- unless @ci_project.new_record?
= link_to 'Remove Project', ci_project_path(@ci_project), method: :delete, data: { confirm: 'Project will be removed. Are you sure?' }, class: 'btn btn-danger pull-right'
.alert.alert-danger
%p
There are NO runners to build this project.
%br
You can add Specific runner for this project on Runners page
- if current_user.admin
or add Shared runner for whole application in admin area.
- page_title "CI Settings"
- if no_runners_for_project?(@ci_project)
= render 'no_runners'
= render 'form'
- page_title "CI Web Hooks"
%h3.page-title
CI Web hooks
%p.light
Web Hooks can be used for binding events when build completed.
%hr.clearfix
= form_for @web_hook, url: namespace_project_ci_web_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
-if @web_hook.errors.any?
.alert.alert-danger
- @web_hook.errors.full_messages.each do |msg|
%p= msg
.form-group
= f.label :url, "URL", class: 'control-label'
.col-sm-10
= f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json'
.form-actions
= f.submit "Add Web Hook", class: "btn btn-create"
-if @web_hooks.any?
%h4 Activated web hooks (#{@web_hooks.count})
.table-holder
%table.table
- @web_hooks.each do |hook|
%tr
%td
.clearfix
%span.monospace= hook.url
%td
.pull-right
- if @ci_project.commits.any?
= link_to 'Test Hook', test_namespace_project_ci_web_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
= link_to 'Remove', namespace_project_ci_web_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
%h4 Web Hook data example
:erb
<pre>
<code>
{
"build_id": 2,
"build_name":"rspec_linux"
"build_status": "failed",
"build_started_at": "2014-05-05T18:01:02.563Z",
"build_finished_at": "2014-05-05T18:01:07.611Z",
"project_id": 1,
"project_name": "Brightbox \/ Brightbox Cli",
"gitlab_url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli",
"ref": "master",
"sha": "a26cf5de9ed9827746d4970872376b10d9325f40",
"before_sha": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
"push_data": {
"before": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
"after": "a26cf5de9ed9827746d4970872376b10d9325f40",
"ref": "refs\/heads\/master",
"user_id": 1,
"user_name": "Administrator",
"project_id": 5,
"repository": {
"name": "Brightbox Cli",
"url": "dzaporozhets@localhost:brightbox\/brightbox-cli.git",
"description": "Voluptatibus quae error consectetur voluptas dolores vel excepturi possimus.",
"homepage": "http:\/\/localhost:3000\/brightbox\/brightbox-cli"
},
"commits": [
{
"id": "a26cf5de9ed9827746d4970872376b10d9325f40",
"message": "Release v1.2.2",
"timestamp": "2014-04-22T16:46:42+03:00",
"url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli\/commit\/a26cf5de9ed9827746d4970872376b10d9325f40",
"author": {
"name": "Paul Thornthwaite",
"email": "tokengeek@gmail.com"
}
},
{
"id": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
"message": "Fix server user data update\n\nIncorrect condition was being used so Base64 encoding option was having\nopposite effect from desired.",
"timestamp": "2014-04-11T18:17:26+03:00",
"url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli\/commit\/34f57f6ba3ed0c21c5e361bbb041c3591411176c",
"author": {
"name": "Paul Thornthwaite",
"email": "tokengeek@gmail.com"
}
}
],
"total_commits_count": 2,
"ci_yaml_file":"rspec_linux:\r\n script: ls\r\n"
}
}
</code>
</pre>
.gray-content-block.middle-block .gray-content-block.middle-block
.pull-right .pull-right
- if @ci_project && can?(current_user, :manage_builds, @ci_commit.gl_project) - if can?(current_user, :manage_builds, @ci_commit.project)
- if @ci_commit.builds.latest.failed.any?(&:retryable?) - if @ci_commit.builds.latest.failed.any?(&:retryable?)
= link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.gl_project.namespace, @ci_commit.gl_project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
- if @ci_commit.builds.running_or_pending.any? - if @ci_commit.builds.running_or_pending.any?
= link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.gl_project.namespace, @ci_commit.gl_project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
.oneline .oneline
= pluralize @statuses.count(:id), "build" = pluralize @statuses.count(:id), "build"
- if defined?(link_to_commit) && link_to_commit - if defined?(link_to_commit) && link_to_commit
for commit for commit
= link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.gl_project.namespace, @ci_commit.gl_project, @ci_commit.sha), class: "monospace" = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace"
- if @ci_commit.duration > 0 - if @ci_commit.duration > 0
in in
= time_interval_in_words @ci_commit.duration = time_interval_in_words @ci_commit.duration
...@@ -22,8 +22,9 @@ ...@@ -22,8 +22,9 @@
%ul %ul
- @ci_commit.yaml_errors.split(",").each do |error| - @ci_commit.yaml_errors.split(",").each do |error|
%li= error %li= error
You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
- if @ci_commit.gl_project.builds_enabled? && !@ci_commit.ci_yaml_file - if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file
.bs-callout.bs-callout-warning .bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit \.gitlab-ci.yml not found in this commit
...@@ -38,12 +39,12 @@ ...@@ -38,12 +39,12 @@
%th Name %th Name
%th Duration %th Duration
%th Finished at %th Finished at
- if @ci_project && @ci_project.coverage_enabled? - if @ci_commit.project.build_coverage_enabled?
%th Coverage %th Coverage
%th %th
- @ci_commit.refs.each do |ref| - @ci_commit.refs.each do |ref|
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered, = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered,
locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true, allow_retry: true } locals: { coverage: @ci_commit.project.build_coverage_enabled?, stage: true, allow_retry: true }
- if @ci_commit.retried.any? - if @ci_commit.retried.any?
.gray-content-block.second-block .gray-content-block.second-block
...@@ -60,8 +61,8 @@ ...@@ -60,8 +61,8 @@
%th Name %th Name
%th Duration %th Duration
%th Finished at %th Finished at
- if @ci_project && @ci_project.coverage_enabled? - if @ci_commit.project.build_coverage_enabled?
%th Coverage %th Coverage
%th %th
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried,
locals: { coverage: @ci_project.try(:coverage_enabled?), stage: true } locals: { coverage: @ci_commit.project.build_coverage_enabled?, stage: true }
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
- if current_user && can?(current_user, :download_build_artifacts, @project) && commit_status.download_url - if current_user && can?(current_user, :download_build_artifacts, @project) && commit_status.download_url
= link_to commit_status.download_url, title: 'Download artifacts' do = link_to commit_status.download_url, title: 'Download artifacts' do
%i.fa.fa-download %i.fa.fa-download
- if current_user && can?(current_user, :manage_builds, commit_status.gl_project) - if current_user && can?(current_user, :manage_builds, commit_status.project)
- if commit_status.active? - if commit_status.active?
- if commit_status.cancel_url - if commit_status.cancel_url
= link_to commit_status.cancel_url, method: :post, title: 'Cancel' do = link_to commit_status.cancel_url, method: :post, title: 'Cancel' do
......
...@@ -112,6 +112,62 @@ ...@@ -112,6 +112,62 @@
%hr %hr
= link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
%fieldset.features
%legend
Continuous Integration
.form-group
.col-sm-offset-2.col-sm-10
%p Get recent application code using the following command:
.radio
= f.label :build_allow_git_fetch do
= f.radio_button :build_allow_git_fetch, 'false'
%strong git clone
%br
%span.descr Slower but makes sure you have a clean dir before every build
.radio
= f.label :build_allow_git_fetch do
= f.radio_button :build_allow_git_fetch, 'true'
%strong git fetch
%br
%span.descr Faster
.form-group
= f.label :build_timeout_in_minutes, 'Timeout', class: 'control-label'
.col-sm-10
= f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
%p.help-block per build in minutes
.form-group
= f.label :build_coverage_regex, "Test coverage parsing", class: 'control-label'
.col-sm-10
.input-group
%span.input-group-addon /
= f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
%span.input-group-addon /
%p.help-block
We will use this regular expression to find test coverage output in build trace.
Leave blank if you want to disable this feature
.bs-callout.bs-callout-info
%p Below are examples of regex for existing tools:
%ul
%li
Simplecov (Ruby) -
%code \(\d+.\d+\%\) covered
%li
pytest-cov (Python) -
%code \d+\%\s*$
%li
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
%fieldset.features
%legend
Advanced settings
.form-group
= f.label :runners_token, "CI token", class: 'control-label'
.col-sm-10
= f.text_field :runners_token, class: "form-control", placeholder: 'xEeFCaDAB89'
%p.help-block The secure token used to checkout project.
.form-actions .form-actions
= f.submit 'Save changes', class: "btn btn-save" = f.submit 'Save changes', class: "btn btn-save"
......
- ci_project = @project.gitlab_ci_project
%h4 Overall stats %h4 Overall stats
%ul %ul
%li %li
Total: Total:
%strong= pluralize ci_project.builds.count(:all), 'build' %strong= pluralize @project.builds.count(:all), 'build'
%li %li
Successful: Successful:
%strong= pluralize ci_project.builds.success.count(:all), 'build' %strong= pluralize @project.builds.success.count(:all), 'build'
%li %li
Failed: Failed:
%strong= pluralize ci_project.builds.failed.count(:all), 'build' %strong= pluralize @project.builds.failed.count(:all), 'build'
%li %li
Success ratio: Success ratio:
%strong %strong
#{success_ratio(ci_project.builds.success, ci_project.builds.failed)}% #{success_ratio(@project.builds.success, @project.builds.failed)}%
%li %li
Commits covered: Commits covered:
%strong %strong
= ci_project.commits.count(:all) = @project.ci_commits.count(:all)
...@@ -55,6 +55,13 @@ ...@@ -55,6 +55,13 @@
%strong Merge Request events %strong Merge Request events
%p.light %p.light
This url will be triggered when a merge request is created This url will be triggered when a merge request is created
%div
= f.check_box :build_events, class: 'pull-left'
.prepend-left-20
= f.label :build_events, class: 'list-label' do
%strong Build events
%p.light
This url will be triggered when the build status changes
.form-group .form-group
= f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox' = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
.col-sm-10 .col-sm-10
...@@ -78,7 +85,7 @@ ...@@ -78,7 +85,7 @@
.clearfix .clearfix
%span.monospace= hook.url %span.monospace= hook.url
%p %p
- %w(push_events tag_push_events issues_events note_events merge_requests_events).each do |trigger| - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
- if hook.send(trigger) - if hook.send(trigger)
%span.label.label-gray= trigger.titleize %span.label.label-gray= trigger.titleize
SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
...@@ -15,10 +15,10 @@ ...@@ -15,10 +15,10 @@
- if runner.belongs_to_one_project? - if runner.belongs_to_one_project?
= link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' = link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- else - else
- runner_project = @ci_project.runner_projects.find_by(runner_id: runner) - runner_project = @project.runner_projects.find_by(runner_id: runner)
= link_to 'Disable for this project', [:ci, @ci_project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' = link_to 'Disable for this project', namespace_project_runner_project_path(@project.namespace, @project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- elsif runner.specific? - elsif runner.specific?
= form_for [:ci, @ci_project, @ci_project.runner_projects.new] do |f| = form_for [@project.namespace, @project, @project.runner_projects.new] do |f|
= f.hidden_field :runner_id, value: runner.id = f.hidden_field :runner_id, value: runner.id
= f.submit 'Enable for this project', class: 'btn btn-sm' = f.submit 'Enable for this project', class: 'btn btn-sm'
.pull-right .pull-right
......
...@@ -3,11 +3,11 @@ ...@@ -3,11 +3,11 @@
.bs-callout.bs-callout-warning .bs-callout.bs-callout-warning
GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X. GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X.
%hr %hr
- if @ci_project.shared_runners_enabled - if @project.shared_runners_enabled?
= link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-warning', method: :post do = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do
Disable shared runners Disable shared runners
- else - else
= link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-success', method: :post do = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-success', method: :post do
Enable shared runners Enable shared runners
&nbsp; for this project &nbsp; for this project
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
%code #{ci_root_url(only_path: false)} %code #{ci_root_url(only_path: false)}
%li %li
Use the following registration token during setup: Use the following registration token during setup:
%code #{@ci_project.token} %code #{@project.runners_token}
%li %li
Start runner! Start runner!
......
...@@ -36,7 +36,8 @@ ...@@ -36,7 +36,8 @@
:plain :plain
curl -X POST \ curl -X POST \
-F token=TOKEN \ -F token=TOKEN \
#{ci_build_trigger_url(@ci_project.id, 'REF_NAME')} -F ref=REF_NAME \
#{builds_trigger_url(@project.id)}
%h3 %h3
Use .gitlab-ci.yml Use .gitlab-ci.yml
...@@ -51,7 +52,7 @@ ...@@ -51,7 +52,7 @@
trigger: trigger:
type: deploy type: deploy
script: script:
- "curl -X POST -F token=TOKEN #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}" - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
%h3 %h3
Pass build variables Pass build variables
...@@ -65,5 +66,6 @@ ...@@ -65,5 +66,6 @@
:plain :plain
curl -X POST \ curl -X POST \
-F token=TOKEN \ -F token=TOKEN \
-F "ref=REF_NAME" \
-F "variables[RUN_NIGHTLY_BUILD]=true" \ -F "variables[RUN_NIGHTLY_BUILD]=true" \
#{ci_build_trigger_url(@ci_project.id, 'REF_NAME')} #{builds_trigger_url(@project.id)}
...@@ -10,13 +10,13 @@ ...@@ -10,13 +10,13 @@
%hr %hr
= nested_form_for @ci_project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f| = nested_form_for @project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f|
- if @project.errors.any? - if @project.errors.any?
#error_explanation #error_explanation
%p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:" %p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:"
.alert.alert-error .alert.alert-error
%ul %ul
- @ci_project.errors.full_messages.each do |msg| - @project.errors.full_messages.each do |msg|
%li= msg %li= msg
= f.fields_for :variables do |variable_form| = f.fields_for :variables do |variable_form|
......
...@@ -59,6 +59,15 @@ ...@@ -59,6 +59,15 @@
%strong Merge Request events %strong Merge Request events
%p.light %p.light
This url will be triggered when a merge request is created This url will be triggered when a merge request is created
- if @service.supported_events.include?("build")
%div
= form.check_box :build_events, class: 'pull-left'
.prepend-left-20
= form.label :build_events, class: 'list-label' do
%strong Build events
%p.light
This url will be triggered when a build status changes
- @service.fields.each do |field| - @service.fields.each do |field|
- type = field[:type] - type = field[:type]
......
class BuildEmailWorker
include Sidekiq::Worker
def perform(build_id, recipients, push_data)
recipients.each do |recipient|
begin
case push_data['build_status']
when 'success'
Notify.build_success_email(build_id, recipient).deliver_now
when 'failed'
Notify.build_fail_email(build_id, recipient).deliver_now
end
# These are input errors and won't be corrected even if Sidekiq retries
rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
logger.info("Failed to send e-mail for project '#{push_data['project_name']}' to #{recipient}: #{e}")
end
end
end
end
module Ci
class HipChatNotifierWorker
include Sidekiq::Worker
def perform(message, options={})
room = options.delete('room')
token = options.delete('token')
server = options.delete('server')
name = options.delete('service_name')
client_opts = {
api_version: 'v2',
server_url: server
}
client = HipChat::Client.new(token, client_opts)
client[room].send(name, message, options.symbolize_keys)
end
end
end
module Ci
class SlackNotifierWorker
include Sidekiq::Worker
def perform(webhook_url, message, options={})
notifier = Slack::Notifier.new(webhook_url)
notifier.ping(message, options)
end
end
end
module Ci
class WebHookWorker
include Sidekiq::Worker
def perform(hook_id, data)
Ci::WebHook.find(hook_id).execute data
end
end
end
...@@ -24,43 +24,10 @@ Rails.application.routes.draw do ...@@ -24,43 +24,10 @@ Rails.application.routes.draw do
resource :lint, only: [:show, :create] resource :lint, only: [:show, :create]
resources :projects do resources :projects do
collection do
post :add
get :disabled
end
member do member do
get :status, to: 'projects#badge' get :status, to: 'projects#badge'
get :integration get :integration
post :toggle_shared_runners
end end
resources :runner_projects, only: [:create, :destroy]
end
resource :user_sessions do
get :auth
get :callback
end
namespace :admin do
resources :runners, only: [:index, :show, :update, :destroy] do
member do
put :assign_all
get :resume
get :pause
end
end
resources :events, only: [:index]
resources :projects do
resources :runner_projects
end
resources :builds, only: :index
resource :application_settings, only: [:show, :update]
end end
root to: 'projects#index' root to: 'projects#index'
...@@ -271,6 +238,8 @@ Rails.application.routes.draw do ...@@ -271,6 +238,8 @@ Rails.application.routes.draw do
member do member do
put :transfer put :transfer
end end
resources :runner_projects
end end
end end
...@@ -280,6 +249,19 @@ Rails.application.routes.draw do ...@@ -280,6 +249,19 @@ Rails.application.routes.draw do
resources :labels resources :labels
resources :runners, only: [:index, :show, :update, :destroy] do
member do
get :resume
get :pause
end
end
resources :builds, only: :index do
collection do
post :cancel_all
end
end
root to: 'dashboard#index' root to: 'dashboard#index'
end end
...@@ -595,18 +577,6 @@ Rails.application.routes.draw do ...@@ -595,18 +577,6 @@ Rails.application.routes.draw do
resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resource :variables, only: [:show, :update] resource :variables, only: [:show, :update]
resources :triggers, only: [:index, :create, :destroy] resources :triggers, only: [:index, :create, :destroy]
resource :ci_settings, only: [:edit, :update, :destroy]
resources :ci_web_hooks, only: [:index, :create, :destroy] do
member do
get :test
end
end
resources :ci_services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
member do
get :test
end
end
resources :builds, only: [:index, :show] do resources :builds, only: [:index, :show] do
collection do collection do
...@@ -685,7 +655,13 @@ Rails.application.routes.draw do ...@@ -685,7 +655,13 @@ Rails.application.routes.draw do
get :resume get :resume
get :pause get :pause
end end
collection do
post :toggle_shared_runners
end
end end
resources :runner_projects, only: [:create, :destroy]
end end
end end
end end
......
class AddBuildEventsToServices < ActiveRecord::Migration
def up
add_column :services, :build_events, :boolean, default: false, null: false
add_column :web_hooks, :build_events, :boolean, default: false, null: false
end
end
class MigrateCiWebHooks < ActiveRecord::Migration
include Gitlab::Database
def up
execute(
'INSERT INTO web_hooks (url, project_id, type, created_at, updated_at, push_events, issues_events, merge_requests_events, tag_push_events, note_events, build_events) ' \
"SELECT ci_web_hooks.url, projects.id, 'ProjectHook', ci_web_hooks.created_at, ci_web_hooks.updated_at, " \
"#{false_value}, #{false_value}, #{false_value}, #{false_value}, #{false_value}, #{true_value} FROM ci_web_hooks " \
'JOIN ci_projects ON ci_web_hooks.project_id = ci_projects.id ' \
'JOIN projects ON ci_projects.gitlab_id = projects.id'
)
end
end
class MigrateCiEmails < ActiveRecord::Migration
include Gitlab::Database
def up
# This inserts a new service: BuildsEmailService
# It "manually" constructs the properties (JSON-encoded)
# Migrating all ci_projects e-mail related columns
execute(
'INSERT INTO services (project_id, type, created_at, updated_at, active, push_events, issues_events, merge_requests_events, tag_push_events, note_events, build_events, properties) ' \
"SELECT projects.id, 'BuildsEmailService', ci_services.created_at, ci_services.updated_at, " \
"#{true_value}, #{false_value}, #{false_value}, #{false_value}, #{false_value}, #{false_value}, #{true_value}, " \
"CONCAT('{\"notify_only_broken_builds\":\"', #{convert_bool('ci_projects.email_only_broken_builds')}, " \
"'\",\"add_pusher\":\"', #{convert_bool('ci_projects.email_add_pusher')}, " \
"'\",\"recipients\":\"', #{escape_text('ci_projects.email_recipients')}, " \
"'\"}') " \
'FROM ci_services ' \
'JOIN ci_projects ON ci_services.project_id = ci_projects.id ' \
'JOIN projects ON ci_projects.gitlab_id = projects.id ' \
"WHERE ci_services.type = 'Ci::MailService' AND ci_services.active"
)
end
def down
end
# This function escapes double-quotes and slash
def escape_text(name)
"REPLACE(REPLACE(#{name}, '\\', '\\\\'), '\"', '\\\"')"
end
# This function returns 0 or 1 for column
def convert_bool(name)
if Gitlab::Database.postgresql?
# PostgreSQL uses BOOLEAN type
"CASE WHEN #{name} IS TRUE THEN '1' ELSE '0' END"
else
# MySQL uses TINYINT
"#{name}"
end
end
end
class MigrateCiSlackService < ActiveRecord::Migration
include Gitlab::Database
def up
properties_query = 'SELECT properties FROM ci_services ' \
'JOIN ci_projects ON ci_services.project_id=ci_projects.id ' \
"WHERE ci_projects.gitlab_id=services.project_id AND ci_services.type='Ci::SlackService' AND ci_services.active " \
'LIMIT 1'
active_query = 'SELECT 1 FROM ci_services ' \
'JOIN ci_projects ON ci_services.project_id=ci_projects.id ' \
"WHERE ci_projects.gitlab_id=services.project_id AND ci_services.type='Ci::SlackService' AND ci_services.active " \
'LIMIT 1'
# We update the service since services are always generated for project, even if they are inactive
# Activate service and migrate properties if currently the service is not active
execute(
"UPDATE services SET properties=(#{properties_query}), active=#{true_value}, " \
"push_events=#{false_value}, issues_events=#{false_value}, merge_requests_events=#{false_value}, " \
"tag_push_events=#{false_value}, note_events=#{false_value}, build_events=#{true_value} " \
"WHERE NOT services.active AND services.type='SlackService' AND (#{active_query}) IS NOT NULL"
)
# Tick only build_events if the service is already active
execute(
"UPDATE services SET build_events=#{true_value} " \
"WHERE services.active AND services.type='SlackService' AND (#{active_query}) IS NOT NULL"
)
end
def down
end
end
class MigrateCiHipChatService < ActiveRecord::Migration
include Gitlab::Database
def up
# From properties strip `hipchat_` key
properties_query = "SELECT REPLACE(properties, '\"hipchat_', '\"') FROM ci_services " \
'JOIN ci_projects ON ci_services.project_id=ci_projects.id ' \
"WHERE ci_projects.gitlab_id=services.project_id AND ci_services.type='Ci::HipChatService' AND ci_services.active " \
'LIMIT 1'
active_query = 'SELECT 1 FROM ci_services ' \
'JOIN ci_projects ON ci_services.project_id=ci_projects.id ' \
"WHERE ci_projects.gitlab_id=services.project_id AND ci_services.type='Ci::HipChatService' AND ci_services.active " \
'LIMIT 1'
# We update the service since services are always generated for project, even if they are inactive
# Activate service and migrate properties if currently the service is not active
execute(
"UPDATE services SET properties=(#{properties_query}), active=#{true_value}, " \
"push_events=#{false_value}, issues_events=#{false_value}, merge_requests_events=#{false_value}, " \
"tag_push_events=#{false_value}, note_events=#{false_value}, build_events=#{true_value} " \
"WHERE NOT services.active AND services.type='HipchatService' AND (#{active_query}) IS NOT NULL"
)
# Tick only build_events if the service is already active
execute(
"UPDATE services SET build_events=#{true_value} " \
"WHERE services.active AND services.type='HipchatService' AND (#{active_query}) IS NOT NULL"
)
end
def down
end
end
class AddCiToProject < ActiveRecord::Migration
def up
add_column :projects, :ci_id, :integer
add_column :projects, :builds_enabled, :boolean, default: true, null: false
add_column :projects, :shared_runners_enabled, :boolean, default: true, null: false
add_column :projects, :runners_token, :string
add_column :projects, :build_coverage_regex, :string
add_column :projects, :build_allow_git_fetch, :boolean, default: true, null: false
add_column :projects, :build_timeout, :integer, default: 3600, null: false
end
end
class AddProjectIdToCi < ActiveRecord::Migration
def up
add_column :ci_builds, :gl_project_id, :integer
add_column :ci_runner_projects, :gl_project_id, :integer
add_column :ci_triggers, :gl_project_id, :integer
add_column :ci_variables, :gl_project_id, :integer
end
end
class MigrateCiToProject < ActiveRecord::Migration
def up
migrate_project_id_for_table('ci_runner_projects')
migrate_project_id_for_table('ci_triggers')
migrate_project_id_for_table('ci_variables')
migrate_project_id_for_builds
migrate_project_column('id', 'ci_id')
migrate_project_column('shared_runners_enabled', 'shared_runners_enabled')
migrate_project_column('token', 'runners_token')
migrate_project_column('coverage_regex', 'build_coverage_regex')
migrate_project_column('allow_git_fetch', 'build_allow_git_fetch')
migrate_project_column('timeout', 'build_timeout')
migrate_ci_service
end
def migrate_project_id_for_table(table)
subquery = "SELECT gitlab_id FROM ci_projects WHERE ci_projects.id = #{table}.project_id"
execute("UPDATE #{table} SET gl_project_id=(#{subquery}) WHERE gl_project_id IS NULL")
end
def migrate_project_id_for_builds
subquery = 'SELECT gl_project_id FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id'
execute("UPDATE ci_builds SET gl_project_id=(#{subquery}) WHERE gl_project_id IS NULL")
end
def migrate_project_column(column, new_column = nil)
new_column ||= column
subquery = "SELECT ci_projects.#{column} FROM ci_projects WHERE projects.id = ci_projects.gitlab_id"
execute("UPDATE projects SET #{new_column}=(#{subquery}) WHERE #{new_column} IS NULL AND (#{subquery}) IS NOT NULL")
end
def migrate_ci_service
subquery = "SELECT active FROM services WHERE projects.id = services.project_id AND type='GitlabCiService'"
execute("UPDATE projects SET builds_enabled=(#{subquery}) WHERE builds_enabled IS NULL AND (#{subquery}) IS NOT NULL")
end
end
class AddIndexToCiTables < ActiveRecord::Migration
def up
add_index :ci_builds, :gl_project_id
add_index :ci_runner_projects, :gl_project_id
add_index :ci_triggers, :gl_project_id
add_index :ci_variables, :gl_project_id
add_index :projects, :runners_token
add_index :projects, :builds_enabled
add_index :projects, [:builds_enabled, :shared_runners_enabled]
add_index :projects, [:ci_id]
end
end
class DropNullForCiTables < ActiveRecord::Migration
def up
remove_index :ci_variables, :project_id
remove_index :ci_runner_projects, :project_id
change_column_null :ci_triggers, :project_id, true
change_column_null :ci_variables, :project_id, true
change_column_null :ci_runner_projects, :project_id, true
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151203162133) do ActiveRecord::Schema.define(version: 20151210125932) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -110,6 +110,7 @@ ActiveRecord::Schema.define(version: 20151203162133) do ...@@ -110,6 +110,7 @@ ActiveRecord::Schema.define(version: 20151203162133) do
t.string "target_url" t.string "target_url"
t.string "description" t.string "description"
t.text "artifacts_file" t.text "artifacts_file"
t.integer "gl_project_id"
end end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
...@@ -117,6 +118,7 @@ ActiveRecord::Schema.define(version: 20151203162133) do ...@@ -117,6 +118,7 @@ ActiveRecord::Schema.define(version: 20151203162133) do
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree
add_index "ci_builds", ["gl_project_id"], name: "index_ci_builds_on_gl_project_id", using: :btree
add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
...@@ -201,13 +203,14 @@ ActiveRecord::Schema.define(version: 20151203162133) do ...@@ -201,13 +203,14 @@ ActiveRecord::Schema.define(version: 20151203162133) do
add_index "ci_projects", ["shared_runners_enabled"], name: "index_ci_projects_on_shared_runners_enabled", using: :btree add_index "ci_projects", ["shared_runners_enabled"], name: "index_ci_projects_on_shared_runners_enabled", using: :btree
create_table "ci_runner_projects", force: :cascade do |t| create_table "ci_runner_projects", force: :cascade do |t|
t.integer "runner_id", null: false t.integer "runner_id", null: false
t.integer "project_id", null: false t.integer "project_id"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "gl_project_id"
end end
add_index "ci_runner_projects", ["project_id"], name: "index_ci_runner_projects_on_project_id", using: :btree add_index "ci_runner_projects", ["gl_project_id"], name: "index_ci_runner_projects_on_gl_project_id", using: :btree
add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree
create_table "ci_runners", force: :cascade do |t| create_table "ci_runners", force: :cascade do |t|
...@@ -277,24 +280,27 @@ ActiveRecord::Schema.define(version: 20151203162133) do ...@@ -277,24 +280,27 @@ ActiveRecord::Schema.define(version: 20151203162133) do
create_table "ci_triggers", force: :cascade do |t| create_table "ci_triggers", force: :cascade do |t|
t.string "token" t.string "token"
t.integer "project_id", null: false t.integer "project_id"
t.datetime "deleted_at" t.datetime "deleted_at"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "gl_project_id"
end end
add_index "ci_triggers", ["deleted_at"], name: "index_ci_triggers_on_deleted_at", using: :btree add_index "ci_triggers", ["deleted_at"], name: "index_ci_triggers_on_deleted_at", using: :btree
add_index "ci_triggers", ["gl_project_id"], name: "index_ci_triggers_on_gl_project_id", using: :btree
create_table "ci_variables", force: :cascade do |t| create_table "ci_variables", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id"
t.string "key" t.string "key"
t.text "value" t.text "value"
t.text "encrypted_value" t.text "encrypted_value"
t.string "encrypted_value_salt" t.string "encrypted_value_salt"
t.string "encrypted_value_iv" t.string "encrypted_value_iv"
t.integer "gl_project_id"
end end
add_index "ci_variables", ["project_id"], name: "index_ci_variables_on_project_id", using: :btree add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree
create_table "ci_web_hooks", force: :cascade do |t| create_table "ci_web_hooks", force: :cascade do |t|
t.string "url", null: false t.string "url", null: false
...@@ -649,13 +655,24 @@ ActiveRecord::Schema.define(version: 20151203162133) do ...@@ -649,13 +655,24 @@ ActiveRecord::Schema.define(version: 20151203162133) do
t.string "import_source" t.string "import_source"
t.integer "commit_count", default: 0 t.integer "commit_count", default: 0
t.text "import_error" t.text "import_error"
end t.integer "ci_id"
t.boolean "builds_enabled", default: true, null: false
t.boolean "shared_runners_enabled", default: true, null: false
t.string "runners_token"
t.string "build_coverage_regex"
t.boolean "build_allow_git_fetch", default: true, null: false
t.integer "build_timeout", default: 3600, null: false
end
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
add_index "projects", ["builds_enabled"], name: "index_projects_on_builds_enabled", using: :btree
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree
add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree
add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
add_index "projects", ["path"], name: "index_projects_on_path", using: :btree add_index "projects", ["path"], name: "index_projects_on_path", using: :btree
add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
...@@ -706,6 +723,7 @@ ActiveRecord::Schema.define(version: 20151203162133) do ...@@ -706,6 +723,7 @@ ActiveRecord::Schema.define(version: 20151203162133) do
t.boolean "merge_requests_events", default: true t.boolean "merge_requests_events", default: true
t.boolean "tag_push_events", default: true t.boolean "tag_push_events", default: true
t.boolean "note_events", default: true, null: false t.boolean "note_events", default: true, null: false
t.boolean "build_events", default: false, null: false
end end
add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree
...@@ -854,6 +872,7 @@ ActiveRecord::Schema.define(version: 20151203162133) do ...@@ -854,6 +872,7 @@ ActiveRecord::Schema.define(version: 20151203162133) do
t.boolean "tag_push_events", default: false t.boolean "tag_push_events", default: false
t.boolean "note_events", default: false, null: false t.boolean "note_events", default: false, null: false
t.boolean "enable_ssl_verification", default: true t.boolean "enable_ssl_verification", default: true
t.boolean "build_events", default: false, null: false
end end
add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
......
...@@ -32,7 +32,6 @@ Parameters: ...@@ -32,7 +32,6 @@ Parameters:
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
```json ```json
[ [
...@@ -137,7 +136,6 @@ Parameters: ...@@ -137,7 +136,6 @@ Parameters:
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
### List ALL projects ### List ALL projects
...@@ -153,7 +151,6 @@ Parameters: ...@@ -153,7 +151,6 @@ Parameters:
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
### Get single project ### Get single project
......
...@@ -7,12 +7,6 @@ Feature: Project Services ...@@ -7,12 +7,6 @@ Feature: Project Services
When I visit project "Shop" services page When I visit project "Shop" services page
Then I should see list of available services Then I should see list of available services
Scenario: Activate gitlab-ci service
When I visit project "Shop" services page
And I click gitlab-ci service link
And I fill gitlab-ci settings
Then I should see service settings saved
Scenario: Activate hipchat service Scenario: Activate hipchat service
When I visit project "Shop" services page When I visit project "Shop" services page
And I click hipchat service link And I click hipchat service link
......
...@@ -32,6 +32,7 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps ...@@ -32,6 +32,7 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
page.check('Comments') page.check('Comments')
page.check('Issues events') page.check('Issues events')
page.check('Merge Request events') page.check('Merge Request events')
page.check('Build events')
click_on 'Save' click_on 'Save'
end end
...@@ -39,6 +40,7 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps ...@@ -39,6 +40,7 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
fill_in 'Webhook', with: 'http://localhost' fill_in 'Webhook', with: 'http://localhost'
fill_in 'Username', with: 'test_user' fill_in 'Username', with: 'test_user'
fill_in 'Channel', with: '#test_channel' fill_in 'Channel', with: '#test_channel'
page.check('Notify only broken builds')
end end
step 'I should see service template settings saved' do step 'I should see service template settings saved' do
......
...@@ -104,7 +104,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -104,7 +104,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'commit has ci status' do step 'commit has ci status' do
@project.enable_ci @project.enable_ci
ci_commit = create :ci_commit, gl_project: @project, sha: sample_commit.id ci_commit = create :ci_commit, project: @project, sha: sample_commit.id
create :ci_build, commit: ci_commit create :ci_build, commit: ci_commit
end end
......
...@@ -405,7 +405,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -405,7 +405,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step '"Bug NS-05" has CI status' do step '"Bug NS-05" has CI status' do
project = merge_request.source_project project = merge_request.source_project
project.enable_ci project.enable_ci
ci_commit = create :ci_commit, gl_project: project, sha: merge_request.last_commit.id ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id
create :ci_build, commit: ci_commit create :ci_build, commit: ci_commit
end end
......
...@@ -11,7 +11,6 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -11,7 +11,6 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
expect(page).to have_content 'Project services' expect(page).to have_content 'Project services'
expect(page).to have_content 'Campfire' expect(page).to have_content 'Campfire'
expect(page).to have_content 'HipChat' expect(page).to have_content 'HipChat'
expect(page).to have_content 'GitLab CI'
expect(page).to have_content 'Assembla' expect(page).to have_content 'Assembla'
expect(page).to have_content 'Pushover' expect(page).to have_content 'Pushover'
expect(page).to have_content 'Atlassian Bamboo' expect(page).to have_content 'Atlassian Bamboo'
...@@ -20,15 +19,6 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps ...@@ -20,15 +19,6 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
expect(page).to have_content 'Irker (IRC gateway)' expect(page).to have_content 'Irker (IRC gateway)'
end end
step 'I click gitlab-ci service link' do
click_link 'GitLab CI'
end
step 'I fill gitlab-ci settings' do
check 'Active'
click_button 'Save'
end
step 'I should see service settings saved' do step 'I should see service settings saved' do
expect(find_field('Active').value).to eq '1' expect(find_field('Active').value).to eq '1'
end end
......
...@@ -204,7 +204,7 @@ module SharedProject ...@@ -204,7 +204,7 @@ module SharedProject
step 'project "Shop" has CI build' do step 'project "Shop" has CI build' do
project = Project.find_by(name: "Shop") project = Project.find_by(name: "Shop")
create :ci_commit, gl_project: project, sha: project.commit.sha create :ci_commit, project: project, sha: project.commit.sha
end end
step 'I should see last commit with CI status' do step 'I should see last commit with CI status' do
......
...@@ -53,5 +53,6 @@ module API ...@@ -53,5 +53,6 @@ module API
mount Settings mount Settings
mount Keys mount Keys
mount Tags mount Tags
mount Triggers
end end
end end
...@@ -53,7 +53,7 @@ module API ...@@ -53,7 +53,7 @@ module API
name = params[:name] || params[:context] name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref]) status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref])
status ||= GenericCommitStatus.new(commit: ci_commit, user: current_user) status ||= GenericCommitStatus.new(project: @project, commit: ci_commit, user: current_user)
status.update(attrs) status.update(attrs)
case params[:state].to_s case params[:state].to_s
......
...@@ -45,7 +45,8 @@ module API ...@@ -45,7 +45,8 @@ module API
class ProjectHook < Hook class ProjectHook < Hook
expose :project_id, :push_events expose :project_id, :push_events
expose :issues_events, :merge_requests_events, :tag_push_events, :note_events, :enable_ssl_verification expose :issues_events, :merge_requests_events, :tag_push_events, :note_events, :build_events
expose :enable_ssl_verification
end end
class ForkedFromProject < Grape::Entity class ForkedFromProject < Grape::Entity
...@@ -63,6 +64,7 @@ module API ...@@ -63,6 +64,7 @@ module API
expose :name, :name_with_namespace expose :name, :name_with_namespace
expose :path, :path_with_namespace expose :path, :path_with_namespace
expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :created_at, :last_activity_at expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :created_at, :last_activity_at
expose :shared_runners_enabled
expose :creator_id expose :creator_id
expose :namespace expose :namespace
expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? } expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? }
...@@ -252,7 +254,7 @@ module API ...@@ -252,7 +254,7 @@ module API
class ProjectService < Grape::Entity class ProjectService < Grape::Entity
expose :id, :title, :created_at, :updated_at, :active expose :id, :title, :created_at, :updated_at, :active
expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events, :build_events
# Expose serialized properties # Expose serialized properties
expose :properties do |service, options| expose :properties do |service, options|
field_names = service.fields. field_names = service.fields.
...@@ -359,5 +361,9 @@ module API ...@@ -359,5 +361,9 @@ module API
end end
end end
end end
class TriggerRequest < Grape::Entity
expose :id, :variables
end
end end
end end
...@@ -266,12 +266,7 @@ module API ...@@ -266,12 +266,7 @@ module API
projects = projects.search(params[:search]) projects = projects.search(params[:search])
end end
if params[:ci_enabled_first].present? projects.reorder(project_order_by => project_sort)
projects.includes(:gitlab_ci_service).
reorder("services.active DESC, projects.#{project_order_by} #{project_sort}")
else
projects.reorder(project_order_by => project_sort)
end
end end
def project_order_by def project_order_by
......
...@@ -45,6 +45,7 @@ module API ...@@ -45,6 +45,7 @@ module API
:merge_requests_events, :merge_requests_events,
:tag_push_events, :tag_push_events,
:note_events, :note_events,
:build_events,
:enable_ssl_verification :enable_ssl_verification
] ]
@hook = user_project.hooks.new(attrs) @hook = user_project.hooks.new(attrs)
...@@ -77,6 +78,7 @@ module API ...@@ -77,6 +78,7 @@ module API
:merge_requests_events, :merge_requests_events,
:tag_push_events, :tag_push_events,
:note_events, :note_events,
:build_events,
:enable_ssl_verification :enable_ssl_verification
] ]
......
...@@ -82,6 +82,7 @@ module API ...@@ -82,6 +82,7 @@ module API
# builds_enabled (optional) # builds_enabled (optional)
# wiki_enabled (optional) # wiki_enabled (optional)
# snippets_enabled (optional) # snippets_enabled (optional)
# shared_runners_enabled (optional)
# namespace_id (optional) - defaults to user namespace # namespace_id (optional) - defaults to user namespace
# public (optional) - if true same as setting visibility_level = 20 # public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - 0 by default # visibility_level (optional) - 0 by default
...@@ -98,6 +99,7 @@ module API ...@@ -98,6 +99,7 @@ module API
:builds_enabled, :builds_enabled,
:wiki_enabled, :wiki_enabled,
:snippets_enabled, :snippets_enabled,
:shared_runners_enabled,
:namespace_id, :namespace_id,
:public, :public,
:visibility_level, :visibility_level,
...@@ -126,6 +128,7 @@ module API ...@@ -126,6 +128,7 @@ module API
# builds_enabled (optional) # builds_enabled (optional)
# wiki_enabled (optional) # wiki_enabled (optional)
# snippets_enabled (optional) # snippets_enabled (optional)
# shared_runners_enabled (optional)
# public (optional) - if true same as setting visibility_level = 20 # public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) # visibility_level (optional)
# import_url (optional) # import_url (optional)
...@@ -142,6 +145,7 @@ module API ...@@ -142,6 +145,7 @@ module API
:builds_enabled, :builds_enabled,
:wiki_enabled, :wiki_enabled,
:snippets_enabled, :snippets_enabled,
:shared_runners_enabled,
:public, :public,
:visibility_level, :visibility_level,
:import_url] :import_url]
...@@ -183,6 +187,7 @@ module API ...@@ -183,6 +187,7 @@ module API
# builds_enabled (optional) # builds_enabled (optional)
# wiki_enabled (optional) # wiki_enabled (optional)
# snippets_enabled (optional) # snippets_enabled (optional)
# shared_runners_enabled (optional)
# public (optional) - if true same as setting visibility_level = 20 # public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - visibility level of a project # visibility_level (optional) - visibility level of a project
# Example Request # Example Request
...@@ -197,6 +202,7 @@ module API ...@@ -197,6 +202,7 @@ module API
:builds_enabled, :builds_enabled,
:wiki_enabled, :wiki_enabled,
:snippets_enabled, :snippets_enabled,
:shared_runners_enabled,
:public, :public,
:visibility_level] :visibility_level]
attrs = map_public_to_visibility_level(attrs) attrs = map_public_to_visibility_level(attrs)
......
module API
# Triggers API
class Triggers < Grape::API
resource :projects do
# Trigger a GitLab project build
#
# Parameters:
# id (required) - The ID of a CI project
# ref (required) - The name of project's branch or tag
# token (required) - The uniq token of trigger
# variables (optional) - The list of variables to be injected into build
# Example Request:
# POST /projects/:id/trigger/builds
post ":id/trigger/builds" do
required_attributes! [:ref, :token]
project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id])
trigger = Ci::Trigger.find_by_token(params[:token].to_s)
not_found! unless project && trigger
unauthorized! unless trigger.project == project
# validate variables
variables = params[:variables]
if variables
unless variables.is_a?(Hash)
render_api_error!('variables needs to be a hash', 400)
end
unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
render_api_error!('variables needs to be a map of key-valued strings', 400)
end
# convert variables from Mash to Hash
variables = variables.to_h
end
# create request and trigger builds
trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
if trigger_request
present trigger_request, with: Entities::TriggerRequest
else
errors = 'No builds created'
render_api_error!(errors, 400)
end
end
end
end
end
...@@ -30,9 +30,7 @@ module Ci ...@@ -30,9 +30,7 @@ module Ci
helpers Gitlab::CurrentSettings helpers Gitlab::CurrentSettings
mount Builds mount Builds
mount Commits
mount Runners mount Runners
mount Projects
mount Triggers mount Triggers
end end
end end
......
module Ci
module API
class Commits < Grape::API
resource :commits do
# Get list of commits per project
#
# Parameters:
# project_id (required) - The ID of a project
# project_token (requires) - Project token
# page (optional)
# per_page (optional) - items per request (default is 20)
#
get do
required_attributes! [:project_id, :project_token]
project = Ci::Project.find(params[:project_id])
authenticate_project_token!(project)
commits = project.commits.page(params[:page]).per(params[:per_page] || 20)
present commits, with: Entities::CommitWithBuilds
end
# Create a commit
#
# Parameters:
# project_id (required) - The ID of a project
# project_token (requires) - Project token
# data (required) - GitLab push data
#
# Sample GitLab push data:
# {
# "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
# "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
# "ref": "refs/heads/master",
# "commits": [
# {
# "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
# "message": "Update Catalan translation to e38cb41.",
# "timestamp": "2011-12-12T14:27:31+02:00",
# "url": "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
# "author": {
# "name": "Jordi Mallach",
# "email": "jordi@softcatala.org",
# }
# }, .... more commits
# ]
# }
#
# Example Request:
# POST /commits
post do
required_attributes! [:project_id, :data, :project_token]
project = Ci::Project.find(params[:project_id])
authenticate_project_token!(project)
commit = Ci::CreateCommitService.new.execute(project, current_user, params[:data])
if commit.persisted?
present commit, with: Entities::CommitWithBuilds
else
errors = commit.errors.full_messages.join(", ")
render_api_error!(errors, 400)
end
end
end
end
end
end
...@@ -37,15 +37,6 @@ module Ci ...@@ -37,15 +37,6 @@ module Ci
expose :id, :token expose :id, :token
end end
class Project < Grape::Entity
expose :id, :name, :token, :default_ref, :gitlab_url, :path,
:always_build, :polling_interval, :public, :ssh_url_to_repo, :gitlab_id
expose :timeout do |model|
model.timeout
end
end
class RunnerProject < Grape::Entity class RunnerProject < Grape::Entity
expose :id, :project_id, :runner_id expose :id, :project_id, :runner_id
end end
......
...@@ -13,10 +13,6 @@ module Ci ...@@ -13,10 +13,6 @@ module Ci
forbidden! unless current_runner forbidden! unless current_runner
end end
def authenticate_project_token!(project)
forbidden! unless project.valid_token?(params[:project_token])
end
def authenticate_build_token!(build) def authenticate_build_token!(build)
token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s
forbidden! unless token && build.valid_token?(token) forbidden! unless token && build.valid_token?(token)
......
module Ci
module API
# Projects API
class Projects < Grape::API
before { authenticate! }
resource :projects do
# Register new webhook for project
#
# Parameters
# project_id (required) - The ID of a project
# web_hook (required) - WebHook URL
# Example Request
# POST /projects/:project_id/webhooks
post ":project_id/webhooks" do
required_attributes! [:web_hook]
project = Ci::Project.find(params[:project_id])
unauthorized! unless can?(current_user, :admin_project, project.gl_project)
web_hook = project.web_hooks.new({ url: params[:web_hook] })
if web_hook.save
present web_hook, with: Entities::WebHook
else
errors = web_hook.errors.full_messages.join(", ")
render_api_error!(errors, 400)
end
end
# Retrieve all Gitlab CI projects that the user has access to
#
# Example Request:
# GET /projects
get do
gitlab_projects = current_user.authorized_projects
gitlab_projects = filter_projects(gitlab_projects)
gitlab_projects = paginate gitlab_projects
ids = gitlab_projects.map { |project| project.id }
projects = Ci::Project.where("gitlab_id IN (?)", ids).load
present projects, with: Entities::Project
end
# Retrieve all Gitlab CI projects that the user owns
#
# Example Request:
# GET /projects/owned
get "owned" do
gitlab_projects = current_user.owned_projects
gitlab_projects = filter_projects(gitlab_projects)
gitlab_projects = paginate gitlab_projects
ids = gitlab_projects.map { |project| project.id }
projects = Ci::Project.where("gitlab_id IN (?)", ids).load
present projects, with: Entities::Project
end
# Retrieve info for a Gitlab CI project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# GET /projects/:id
get ":id" do
project = Ci::Project.find(params[:id])
unauthorized! unless can?(current_user, :read_project, project.gl_project)
present project, with: Entities::Project
end
# Create Gitlab CI project using Gitlab project info
#
# Parameters:
# gitlab_id (required) - The gitlab id of the project
# default_ref - The branch to run against (defaults to `master`)
# Example Request:
# POST /projects
post do
required_attributes! [:gitlab_id]
filtered_params = {
gitlab_id: params[:gitlab_id],
# we accept gitlab_url for backward compatibility for a while (added to 7.11)
default_ref: params[:default_ref] || 'master'
}
project = Ci::Project.new(filtered_params)
project.build_missing_services
if project.save
present project, with: Entities::Project
else
errors = project.errors.full_messages.join(", ")
render_api_error!(errors, 400)
end
end
# Update a Gitlab CI project
#
# Parameters:
# id (required) - The ID of a project
# default_ref - The branch to run against (defaults to `master`)
# Example Request:
# PUT /projects/:id
put ":id" do
project = Ci::Project.find(params[:id])
unauthorized! unless can?(current_user, :admin_project, project.gl_project)
attrs = attributes_for_keys [:default_ref]
if project.update_attributes(attrs)
present project, with: Entities::Project
else
errors = project.errors.full_messages.join(", ")
render_api_error!(errors, 400)
end
end
# Remove a Gitlab CI project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# DELETE /projects/:id
delete ":id" do
project = Ci::Project.find(params[:id])
unauthorized! unless can?(current_user, :admin_project, project.gl_project)
project.destroy
end
# Link a Gitlab CI project to a runner
#
# Parameters:
# id (required) - The ID of a CI project
# runner_id (required) - The ID of a runner
# Example Request:
# POST /projects/:id/runners/:runner_id
post ":id/runners/:runner_id" do
project = Ci::Project.find(params[:id])
runner = Ci::Runner.find(params[:runner_id])
unauthorized! unless can?(current_user, :admin_project, project.gl_project)
options = {
project_id: project.id,
runner_id: runner.id
}
runner_project = Ci::RunnerProject.new(options)
if runner_project.save
present runner_project, with: Entities::RunnerProject
else
errors = project.errors.full_messages.join(", ")
render_api_error!(errors, 400)
end
end
# Remove a Gitlab CI project from a runner
#
# Parameters:
# id (required) - The ID of a CI project
# runner_id (required) - The ID of a runner
# Example Request:
# DELETE /projects/:id/runners/:runner_id
delete ":id/runners/:runner_id" do
project = Ci::Project.find(params[:id])
runner = Ci::Runner.find(params[:runner_id])
unauthorized! unless can?(current_user, :admin_project, project.gl_project)
options = {
project_id: project.id,
runner_id: runner.id
}
runner_project = Ci::RunnerProject.find_by(options)
if runner_project.present?
runner_project.destroy
else
not_found!
end
end
end
end
end
end
...@@ -3,17 +3,6 @@ module Ci ...@@ -3,17 +3,6 @@ module Ci
# Runners API # Runners API
class Runners < Grape::API class Runners < Grape::API
resource :runners do resource :runners do
# Get list of all available runners
#
# Example Request:
# GET /runners
get do
authenticate!
runners = Ci::Runner.all
present runners, with: Entities::Runner
end
# Delete runner # Delete runner
# Parameters: # Parameters:
# token (required) - The unique token of runner # token (required) - The unique token of runner
...@@ -47,7 +36,7 @@ module Ci ...@@ -47,7 +36,7 @@ module Ci
tag_list: params[:tag_list], tag_list: params[:tag_list],
is_shared: true is_shared: true
) )
elsif project = Ci::Project.find_by(token: params[:token]) elsif project = Project.find_by(runners_token: params[:token])
# Create a specific runner for project. # Create a specific runner for project.
project.runners.create( project.runners.create(
description: params[:description], description: params[:description],
......
...@@ -14,7 +14,7 @@ module Ci ...@@ -14,7 +14,7 @@ module Ci
post ":id/refs/:ref/trigger" do post ":id/refs/:ref/trigger" do
required_attributes! [:token] required_attributes! [:token]
project = Ci::Project.find(params[:id]) project = Project.find_by(ci_id: params[:id].to_i)
trigger = Ci::Trigger.find_by_token(params[:token].to_s) trigger = Ci::Trigger.find_by_token(params[:token].to_s)
not_found! unless project && trigger not_found! unless project && trigger
unauthorized! unless trigger.project == project unauthorized! unless trigger.project == project
......
...@@ -60,7 +60,7 @@ module Ci ...@@ -60,7 +60,7 @@ module Ci
class BuildTime < Chart class BuildTime < Chart
def collect def collect
commits = project.commits.last(30) commits = project.ci_commits.last(30)
commits.each do |commit| commits.each do |commit|
@labels << commit.short_sha @labels << commit.short_sha
......
module Ci
module CurrentSettings
def current_application_settings
key = :ci_current_application_settings
RequestStore.store[key] ||= begin
if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('ci_application_settings')
Ci::ApplicationSetting.current || Ci::ApplicationSetting.create_from_defaults
else
fake_application_settings
end
end
end
def fake_application_settings
OpenStruct.new(
all_broken_builds: Ci::Settings.gitlab_ci['all_broken_builds'],
add_pusher: Ci::Settings.gitlab_ci['add_pusher'],
)
end
end
end
module Ci
module Git
BLANK_SHA = '0' * 40
end
end
module Ci
class Scheduler
def perform
projects = Ci::Project.where(always_build: true).all
projects.each do |project|
last_commit = project.commits.last
next unless last_commit && last_commit.last_build
interval = project.polling_interval
if (last_commit.last_build.created_at + interval.hours) < Time.now
last_commit.retry
end
end
end
end
end
...@@ -77,7 +77,9 @@ module Grack ...@@ -77,7 +77,9 @@ module Grack
if project && matched_login.present? && git_cmd == 'git-upload-pack' if project && matched_login.present? && git_cmd == 'git-upload-pack'
underscored_service = matched_login['s'].underscore underscored_service = matched_login['s'].underscore
if Service.available_services_names.include?(underscored_service) if underscored_service == 'gitlab_ci'
return project && project.valid_build_token?(password)
elsif Service.available_services_names.include?(underscored_service)
service_method = "#{underscored_service}_service" service_method = "#{underscored_service}_service"
service = project.send(service_method) service = project.send(service_method)
......
module Gitlab
class BuildDataBuilder
class << self
def build(build)
project = build.project
commit = build.commit
user = build.user
data = {
object_kind: 'build',
ref: build.ref,
tag: build.tag,
before_sha: build.before_sha,
sha: build.sha,
# TODO: should this be not prefixed with build_?
# Leaving this way to have backward compatibility
build_id: build.id,
build_name: build.name,
build_stage: build.stage,
build_status: build.status,
build_started_at: build.started_at,
build_finished_at: build.finished_at,
build_duration: build.duration,
# TODO: do we still need it?
project_id: project.id,
project_name: project.name_with_namespace,
user: {
id: user.try(:id),
name: user.try(:name),
email: user.try(:email),
},
commit: {
id: commit.id,
sha: commit.sha,
message: commit.git_commit_message,
author_name: commit.git_author_name,
author_email: commit.git_author_email,
status: commit.status,
duration: commit.duration,
started_at: commit.started_at,
finished_at: commit.finished_at,
},
repository: {
name: project.name,
url: project.url_to_repo,
description: project.description,
homepage: project.web_url,
git_http_url: project.http_url_to_repo,
git_ssh_url: project.ssh_url_to_repo,
visibility_level: project.visibility_level
},
}
data
end
end
end
end
...@@ -7,5 +7,23 @@ module Gitlab ...@@ -7,5 +7,23 @@ module Gitlab
def self.postgresql? def self.postgresql?
ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql' ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql'
end end
def true_value
case ActiveRecord::Base.connection.adapter_name.downcase
when 'postgresql'
"'t'"
else
1
end
end
def false_value
case ActiveRecord::Base.connection.adapter_name.downcase
when 'postgresql'
"'f'"
else
0
end
end
end end
end end
namespace :ci do
desc "GitLab CI | Clean running builds"
task schedule_builds: :environment do
Ci::Scheduler.new.perform
end
end
...@@ -42,6 +42,10 @@ FactoryGirl.define do ...@@ -42,6 +42,10 @@ FactoryGirl.define do
commit factory: :ci_commit commit factory: :ci_commit
after(:build) do |build, evaluator|
build.project = build.commit.project
end
factory :ci_not_started_build do factory :ci_not_started_build do
started_at nil started_at nil
finished_at nil finished_at nil
......
...@@ -21,7 +21,7 @@ FactoryGirl.define do ...@@ -21,7 +21,7 @@ FactoryGirl.define do
factory :ci_empty_commit, class: Ci::Commit do factory :ci_empty_commit, class: Ci::Commit do
sha '97de212e80737a608d939f648d959671fb0a0142' sha '97de212e80737a608d939f648d959671fb0a0142'
gl_project factory: :empty_project project factory: :empty_project
factory :ci_commit_without_jobs do factory :ci_commit_without_jobs do
after(:build) do |commit| after(:build) do |commit|
......
# == Schema Information
#
# Table name: events
#
# id :integer not null, primary key
# project_id :integer
# user_id :integer
# is_admin :integer
# description :text
# created_at :datetime
# updated_at :datetime
#
FactoryGirl.define do
factory :ci_event, class: Ci::Event do
sequence :description do |n|
"updated project settings#{n}"
end
factory :ci_admin_event do
is_admin true
end
end
end
# == Schema Information
#
# Table name: projects
#
# id :integer not null, primary key
# name :string(255) not null
# timeout :integer default(3600), not null
# created_at :datetime
# updated_at :datetime
# token :string(255)
# default_ref :string(255)
# path :string(255)
# always_build :boolean default(FALSE), not null
# polling_interval :integer
# public :boolean default(FALSE), not null
# ssh_url_to_repo :string(255)
# gitlab_id :integer
# allow_git_fetch :boolean default(TRUE), not null
# email_recipients :string(255) default(""), not null
# email_add_pusher :boolean default(TRUE), not null
# email_only_broken_builds :boolean default(TRUE), not null
# skip_refs :string(255)
# coverage_regex :string(255)
# shared_runners_enabled :boolean default(FALSE)
# generated_yaml_config :text
#
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :ci_project_without_token, class: Ci::Project do
default_ref 'master'
shared_runners_enabled false
factory :ci_project do
token 'iPWx6WM4lhHNedGfBpPJNP'
end
initialize_with do
# TODO:
# this is required, because builds_enabled is initialized when Project is created
# and this create gitlab_ci_project if builds is set to true
# here we take created gitlab_ci_project and update it's attributes
ci_project = create(:empty_project).ensure_gitlab_ci_project
ci_project.update_attributes(attributes)
ci_project
end
end
end
...@@ -14,6 +14,6 @@ ...@@ -14,6 +14,6 @@
FactoryGirl.define do FactoryGirl.define do
factory :ci_runner_project, class: Ci::RunnerProject do factory :ci_runner_project, class: Ci::RunnerProject do
runner_id 1 runner_id 1
project_id 1 gl_project_id 1
end end
end end
FactoryGirl.define do
factory :ci_web_hook, class: Ci::WebHook do
sequence(:url) { FFaker::Internet.uri('http') }
project factory: :ci_project
end
end
...@@ -5,17 +5,16 @@ describe "Admin Builds" do ...@@ -5,17 +5,16 @@ describe "Admin Builds" do
let(:build) { FactoryGirl.create :ci_build, commit: commit } let(:build) { FactoryGirl.create :ci_build, commit: commit }
before do before do
skip_ci_admin_auth login_as :admin
login_as :user
end end
describe "GET /admin/builds" do describe "GET /admin/builds" do
before do before do
build build
visit ci_admin_builds_path visit admin_builds_path
end end
it { expect(page).to have_content "All builds" } it { expect(page).to have_content "Running" }
it { expect(page).to have_content build.short_sha } it { expect(page).to have_content build.short_sha }
end end
...@@ -26,43 +25,43 @@ describe "Admin Builds" do ...@@ -26,43 +25,43 @@ describe "Admin Builds" do
FactoryGirl.create :ci_build, commit: commit, status: "success" FactoryGirl.create :ci_build, commit: commit, status: "success"
FactoryGirl.create :ci_build, commit: commit, status: "failed" FactoryGirl.create :ci_build, commit: commit, status: "failed"
visit ci_admin_builds_path visit admin_builds_path
within ".center-top-menu" do
click_on "All"
end
expect(page.all(".build-link").size).to eq(4) expect(page.all(".build-link").size).to eq(4)
end end
it "shows pending builds" do it "shows finished builds" do
build = FactoryGirl.create :ci_build, commit: commit, status: "pending" build = FactoryGirl.create :ci_build, commit: commit, status: "pending"
build1 = FactoryGirl.create :ci_build, commit: commit, status: "running" build1 = FactoryGirl.create :ci_build, commit: commit, status: "running"
build2 = FactoryGirl.create :ci_build, commit: commit, status: "success" build2 = FactoryGirl.create :ci_build, commit: commit, status: "success"
build3 = FactoryGirl.create :ci_build, commit: commit, status: "failed"
visit ci_admin_builds_path visit admin_builds_path
within ".nav.nav-tabs" do within ".center-top-menu" do
click_on "Pending" click_on "Finished"
end end
expect(page.find(".build-link")).to have_content(build.id) expect(page.find(".build-link")).not_to have_content(build.id)
expect(page.find(".build-link")).not_to have_content(build1.id) expect(page.find(".build-link")).not_to have_content(build1.id)
expect(page.find(".build-link")).not_to have_content(build2.id) expect(page.find(".build-link")).to have_content(build2.id)
expect(page.find(".build-link")).not_to have_content(build3.id)
end end
it "shows running builds" do it "shows running builds" do
build = FactoryGirl.create :ci_build, commit: commit, status: "pending" build = FactoryGirl.create :ci_build, commit: commit, status: "pending"
build1 = FactoryGirl.create :ci_build, commit: commit, status: "running"
build2 = FactoryGirl.create :ci_build, commit: commit, status: "success" build2 = FactoryGirl.create :ci_build, commit: commit, status: "success"
build3 = FactoryGirl.create :ci_build, commit: commit, status: "failed" build3 = FactoryGirl.create :ci_build, commit: commit, status: "failed"
visit ci_admin_builds_path visit admin_builds_path
within ".nav.nav-tabs" do within ".center-top-menu" do
click_on "Running" click_on "Running"
end end
expect(page.find(".build-link")).to have_content(build1.id) expect(page.find(".build-link")).to have_content(build.id)
expect(page.find(".build-link")).not_to have_content(build.id)
expect(page.find(".build-link")).not_to have_content(build2.id) expect(page.find(".build-link")).not_to have_content(build2.id)
expect(page.find(".build-link")).not_to have_content(build3.id) expect(page.find(".build-link")).not_to have_content(build3.id)
end end
......
...@@ -10,7 +10,7 @@ describe "Admin Runners" do ...@@ -10,7 +10,7 @@ describe "Admin Runners" do
runner = FactoryGirl.create(:ci_runner) runner = FactoryGirl.create(:ci_runner)
commit = FactoryGirl.create(:ci_commit) commit = FactoryGirl.create(:ci_commit)
FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id)
visit ci_admin_runners_path visit admin_runners_path
end end
it { page.has_text? "Manage Runners" } it { page.has_text? "Manage Runners" }
...@@ -36,9 +36,9 @@ describe "Admin Runners" do ...@@ -36,9 +36,9 @@ describe "Admin Runners" do
let(:runner) { FactoryGirl.create :ci_runner } let(:runner) { FactoryGirl.create :ci_runner }
before do before do
@project1 = FactoryGirl.create(:ci_project) @project1 = FactoryGirl.create(:empty_project)
@project2 = FactoryGirl.create(:ci_project) @project2 = FactoryGirl.create(:empty_project)
visit ci_admin_runner_path(runner) visit admin_runner_path(runner)
end end
describe 'runner info' do describe 'runner info' do
...@@ -53,7 +53,7 @@ describe "Admin Runners" do ...@@ -53,7 +53,7 @@ describe "Admin Runners" do
describe 'search' do describe 'search' do
before do before do
search_form = find('#runner-projects-search') search_form = find('#runner-projects-search')
search_form.fill_in 'search', with: @project1.gl_project.name search_form.fill_in 'search', with: @project1.name
search_form.click_button 'Search' search_form.click_button 'Search'
end end
......
...@@ -7,15 +7,15 @@ describe "Builds" do ...@@ -7,15 +7,15 @@ describe "Builds" do
login_as(:user) login_as(:user)
@commit = FactoryGirl.create :ci_commit @commit = FactoryGirl.create :ci_commit
@build = FactoryGirl.create :ci_build, commit: @commit @build = FactoryGirl.create :ci_build, commit: @commit
@gl_project = @commit.project.gl_project @project = @commit.project
@gl_project.team << [@user, :master] @project.team << [@user, :master]
end end
describe "GET /:project/builds" do describe "GET /:project/builds" do
context "Running scope" do context "Running scope" do
before do before do
@build.run! @build.run!
visit namespace_project_builds_path(@gl_project.namespace, @gl_project) visit namespace_project_builds_path(@project.namespace, @project)
end end
it { expect(page).to have_content 'Running' } it { expect(page).to have_content 'Running' }
...@@ -28,7 +28,7 @@ describe "Builds" do ...@@ -28,7 +28,7 @@ describe "Builds" do
context "Finished scope" do context "Finished scope" do
before do before do
@build.run! @build.run!
visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :finished) visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
end end
it { expect(page).to have_content 'No builds to show' } it { expect(page).to have_content 'No builds to show' }
...@@ -37,8 +37,8 @@ describe "Builds" do ...@@ -37,8 +37,8 @@ describe "Builds" do
context "All builds" do context "All builds" do
before do before do
@gl_project.ci_builds.running_or_pending.each(&:success) @project.builds.running_or_pending.each(&:success)
visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :all) visit namespace_project_builds_path(@project.namespace, @project, scope: :all)
end end
it { expect(page).to have_content 'All' } it { expect(page).to have_content 'All' }
...@@ -52,7 +52,7 @@ describe "Builds" do ...@@ -52,7 +52,7 @@ describe "Builds" do
describe "POST /:project/builds/:id/cancel_all" do describe "POST /:project/builds/:id/cancel_all" do
before do before do
@build.run! @build.run!
visit namespace_project_builds_path(@gl_project.namespace, @gl_project) visit namespace_project_builds_path(@project.namespace, @project)
click_link "Cancel running" click_link "Cancel running"
end end
...@@ -62,7 +62,7 @@ describe "Builds" do ...@@ -62,7 +62,7 @@ describe "Builds" do
describe "GET /:project/builds/:id" do describe "GET /:project/builds/:id" do
before do before do
visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) visit namespace_project_build_path(@project.namespace, @project, @build)
end end
it { expect(page).to have_content @commit.sha[0..7] } it { expect(page).to have_content @commit.sha[0..7] }
...@@ -72,7 +72,7 @@ describe "Builds" do ...@@ -72,7 +72,7 @@ describe "Builds" do
context "Download artifacts" do context "Download artifacts" do
before do before do
@build.update_attributes(artifacts_file: artifacts_file) @build.update_attributes(artifacts_file: artifacts_file)
visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) visit namespace_project_build_path(@project.namespace, @project, @build)
end end
it { expect(page).to have_content 'Download artifacts' } it { expect(page).to have_content 'Download artifacts' }
...@@ -82,7 +82,7 @@ describe "Builds" do ...@@ -82,7 +82,7 @@ describe "Builds" do
describe "POST /:project/builds/:id/cancel" do describe "POST /:project/builds/:id/cancel" do
before do before do
@build.run! @build.run!
visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) visit namespace_project_build_path(@project.namespace, @project, @build)
click_link "Cancel" click_link "Cancel"
end end
...@@ -93,7 +93,7 @@ describe "Builds" do ...@@ -93,7 +93,7 @@ describe "Builds" do
describe "POST /:project/builds/:id/retry" do describe "POST /:project/builds/:id/retry" do
before do before do
@build.run! @build.run!
visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) visit namespace_project_build_path(@project.namespace, @project, @build)
click_link "Cancel" click_link "Cancel"
click_link 'Retry' click_link 'Retry'
end end
...@@ -105,7 +105,7 @@ describe "Builds" do ...@@ -105,7 +105,7 @@ describe "Builds" do
describe "GET /:project/builds/:id/download" do describe "GET /:project/builds/:id/download" do
before do before do
@build.update_attributes(artifacts_file: artifacts_file) @build.update_attributes(artifacts_file: artifacts_file)
visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) visit namespace_project_build_path(@project.namespace, @project, @build)
click_link 'Download artifacts' click_link 'Download artifacts'
end end
......
require 'spec_helper'
describe "Admin Events" do
let(:event) { FactoryGirl.create :ci_admin_event }
before do
skip_ci_admin_auth
login_as :user
end
describe "GET /admin/events" do
before do
event
visit ci_admin_events_path
end
it { expect(page).to have_content "Events" }
it { expect(page).to have_content event.description }
end
end
require 'spec_helper'
describe "Admin Projects" do
let(:project) { FactoryGirl.create :ci_project }
before do
skip_ci_admin_auth
login_as :user
end
describe "GET /admin/projects" do
before do
project
visit ci_admin_projects_path
end
it { expect(page).to have_content "Projects" }
end
end
require 'spec_helper'
describe "CI settings" do
let(:user) { create(:user) }
before { login_as(user) }
before do
@project = FactoryGirl.create :ci_project
@gl_project = @project.gl_project
@gl_project.team << [user, :master]
visit edit_namespace_project_ci_settings_path(@gl_project.namespace, @gl_project)
end
it { expect(page).to have_content 'Build Schedule' }
it "updates configuration" do
fill_in 'Timeout', with: '70'
click_button 'Save changes'
expect(page).to have_content 'was successfully updated'
expect(find_field('Timeout').value).to eq '70'
end
end
require 'spec_helper'
describe 'CI web hooks' do
let(:user) { create(:user) }
before { login_as(user) }
before do
@project = FactoryGirl.create :ci_project
@gl_project = @project.gl_project
@gl_project.team << [user, :master]
visit namespace_project_ci_web_hooks_path(@gl_project.namespace, @gl_project)
end
context 'create a trigger' do
before do
fill_in 'web_hook_url', with: 'http://example.com'
click_on 'Add Web Hook'
end
it { expect(@project.web_hooks.count).to eq(1) }
it 'revokes the trigger' do
click_on 'Remove'
expect(@project.web_hooks.count).to eq(0)
end
end
end
...@@ -9,12 +9,11 @@ describe 'Commits' do ...@@ -9,12 +9,11 @@ describe 'Commits' do
before do before do
login_as :user login_as :user
project.team << [@user, :master] project.team << [@user, :master]
project.ensure_gitlab_ci_project
stub_ci_commit_to_return_yaml_file stub_ci_commit_to_return_yaml_file
end end
let!(:commit) do let!(:commit) do
FactoryGirl.create :ci_commit, gl_project: project, sha: project.commit.sha FactoryGirl.create :ci_commit, project: project, sha: project.commit.sha
end end
let!(:build) { FactoryGirl.create :ci_build, commit: commit } let!(:build) { FactoryGirl.create :ci_build, commit: commit }
...@@ -89,7 +88,7 @@ describe 'Commits' do ...@@ -89,7 +88,7 @@ describe 'Commits' do
end end
describe '.gitlab-ci.yml not found warning' do describe '.gitlab-ci.yml not found warning' do
context 'ci service enabled' do context 'ci builds enabled' do
it "does not show warning" do it "does not show warning" do
visit ci_status_path(commit) visit ci_status_path(commit)
expect(page).not_to have_content '.gitlab-ci.yml not found in this commit' expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
...@@ -102,9 +101,9 @@ describe 'Commits' do ...@@ -102,9 +101,9 @@ describe 'Commits' do
end end
end end
context 'ci service disabled' do context 'ci builds disabled' do
before do before do
stub_ci_service_disabled stub_ci_builds_disabled
stub_ci_commit_yaml_file(nil) stub_ci_commit_yaml_file(nil)
visit ci_status_path(commit) visit ci_status_path(commit)
end end
......
...@@ -12,7 +12,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do ...@@ -12,7 +12,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
end end
context "Active build for Merge Request" do context "Active build for Merge Request" do
let!(:ci_commit) { create(:ci_commit, gl_project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } let!(:ci_commit) { create(:ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
let!(:ci_build) { create(:ci_build, commit: ci_commit) } let!(:ci_build) { create(:ci_build, commit: ci_commit) }
before do before do
...@@ -47,7 +47,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do ...@@ -47,7 +47,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
merge_user: user, title: "MepMep", merge_when_build_succeeds: true) merge_user: user, title: "MepMep", merge_when_build_succeeds: true)
end end
let!(:ci_commit) { create(:ci_commit, gl_project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) } let!(:ci_commit) { create(:ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
let!(:ci_build) { create(:ci_build, commit: ci_commit) } let!(:ci_build) { create(:ci_build, commit: ci_commit) }
before do before do
......
...@@ -8,14 +8,14 @@ describe "Runners" do ...@@ -8,14 +8,14 @@ describe "Runners" do
describe "specific runners" do describe "specific runners" do
before do before do
@project = FactoryGirl.create :ci_project @project = FactoryGirl.create :empty_project, shared_runners_enabled: false
@project.gl_project.team << [user, :master] @project.team << [user, :master]
@project2 = FactoryGirl.create :ci_project @project2 = FactoryGirl.create :empty_project
@project2.gl_project.team << [user, :master] @project2.team << [user, :master]
@project3 = FactoryGirl.create :ci_project @project3 = FactoryGirl.create :empty_project
@project3.gl_project.team << [user, :developer] @project3.team << [user, :developer]
@shared_runner = FactoryGirl.create :ci_shared_runner @shared_runner = FactoryGirl.create :ci_shared_runner
@specific_runner = FactoryGirl.create :ci_specific_runner @specific_runner = FactoryGirl.create :ci_specific_runner
...@@ -25,7 +25,7 @@ describe "Runners" do ...@@ -25,7 +25,7 @@ describe "Runners" do
@project2.runners << @specific_runner2 @project2.runners << @specific_runner2
@project3.runners << @specific_runner3 @project3.runners << @specific_runner3
visit runners_path(@project.gl_project) visit runners_path(@project)
end end
before do before do
...@@ -49,7 +49,7 @@ describe "Runners" do ...@@ -49,7 +49,7 @@ describe "Runners" do
it "disables specific runner for project" do it "disables specific runner for project" do
@project2.runners << @specific_runner @project2.runners << @specific_runner
visit runners_path(@project.gl_project) visit runners_path(@project)
within ".activated-specific-runners" do within ".activated-specific-runners" do
click_on "Disable for this project" click_on "Disable for this project"
...@@ -69,9 +69,9 @@ describe "Runners" do ...@@ -69,9 +69,9 @@ describe "Runners" do
describe "shared runners" do describe "shared runners" do
before do before do
@project = FactoryGirl.create :ci_project @project = FactoryGirl.create :empty_project, shared_runners_enabled: false
@project.gl_project.team << [user, :master] @project.team << [user, :master]
visit runners_path(@project.gl_project) visit runners_path(@project)
end end
it "enables shared runners" do it "enables shared runners" do
...@@ -82,14 +82,14 @@ describe "Runners" do ...@@ -82,14 +82,14 @@ describe "Runners" do
describe "show page" do describe "show page" do
before do before do
@project = FactoryGirl.create :ci_project @project = FactoryGirl.create :empty_project
@project.gl_project.team << [user, :master] @project.team << [user, :master]
@specific_runner = FactoryGirl.create :ci_specific_runner @specific_runner = FactoryGirl.create :ci_specific_runner
@project.runners << @specific_runner @project.runners << @specific_runner
end end
it "shows runner information" do it "shows runner information" do
visit runners_path(@project.gl_project) visit runners_path(@project)
click_on @specific_runner.short_sha click_on @specific_runner.short_sha
expect(page).to have_content(@specific_runner.platform) expect(page).to have_content(@specific_runner.platform)
end end
......
...@@ -5,10 +5,9 @@ describe 'Triggers' do ...@@ -5,10 +5,9 @@ describe 'Triggers' do
before { login_as(user) } before { login_as(user) }
before do before do
@project = FactoryGirl.create :ci_project @project = FactoryGirl.create :empty_project
@gl_project = @project.gl_project @project.team << [user, :master]
@gl_project.team << [user, :master] visit namespace_project_triggers_path(@project.namespace, @project)
visit namespace_project_triggers_path(@gl_project.namespace, @gl_project)
end end
context 'create a trigger' do context 'create a trigger' do
......
...@@ -6,13 +6,12 @@ describe "Variables" do ...@@ -6,13 +6,12 @@ describe "Variables" do
describe "specific runners" do describe "specific runners" do
before do before do
@project = FactoryGirl.create :ci_project @project = FactoryGirl.create :empty_project
@gl_project = @project.gl_project @project.team << [user, :master]
@gl_project.team << [user, :master]
end end
it "creates variable", js: true do it "creates variable", js: true do
visit namespace_project_variables_path(@gl_project.namespace, @gl_project) visit namespace_project_variables_path(@project.namespace, @project)
click_on "Add a variable" click_on "Add a variable"
fill_in "Key", with: "SECRET_KEY" fill_in "Key", with: "SECRET_KEY"
fill_in "Value", with: "SECRET_VALUE" fill_in "Value", with: "SECRET_VALUE"
......
...@@ -191,15 +191,10 @@ describe Grack::Auth, lib: true do ...@@ -191,15 +191,10 @@ describe Grack::Auth, lib: true do
context "when a gitlab ci token is provided" do context "when a gitlab ci token is provided" do
let(:token) { "123" } let(:token) { "123" }
let(:gitlab_ci_project) { FactoryGirl.create :ci_project, token: token } let(:project) { FactoryGirl.create :empty_project }
before do before do
project.gitlab_ci_project = gitlab_ci_project project.update_attributes(runners_token: token, builds_enabled: true)
project.save
gitlab_ci_service = project.build_gitlab_ci_service
gitlab_ci_service.active = true
gitlab_ci_service.save
env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials("gitlab-ci-token", token) env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials("gitlab-ci-token", token)
end end
......
require 'spec_helper'
describe 'Gitlab::BuildDataBuilder' do
let(:build) { create(:ci_build) }
describe :build do
let(:data) do
Gitlab::BuildDataBuilder.build(build)
end
it { expect(data).to be_a(Hash) }
it { expect(data[:ref]).to eq(build.ref) }
it { expect(data[:sha]).to eq(build.sha) }
it { expect(data[:tag]).to eq(build.tag) }
it { expect(data[:build_id]).to eq(build.id) }
it { expect(data[:build_status]).to eq(build.status) }
it { expect(data[:project_id]).to eq(build.project.id) }
it { expect(data[:project_name]).to eq(build.project.name_with_namespace) }
end
end
require 'spec_helper'
describe Ci::Notify do
include EmailSpec::Helpers
include EmailSpec::Matchers
before do
@commit = FactoryGirl.create :ci_commit
@build = FactoryGirl.create :ci_build, commit: @commit
end
describe 'build success' do
subject { Ci::Notify.build_success_email(@build.id, 'wow@example.com') }
it 'has the correct subject' do
should have_subject /Build success for/
end
it 'contains name of project' do
should have_body_text /build successful/
end
end
describe 'build fail' do
subject { Ci::Notify.build_fail_email(@build.id, 'wow@example.com') }
it 'has the correct subject' do
should have_subject /Build failed for/
end
it 'contains name of project' do
should have_body_text /build failed/
end
end
end
...@@ -13,6 +13,7 @@ describe Notify do ...@@ -13,6 +13,7 @@ describe Notify do
let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to } let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
let(:recipient) { create(:user, email: 'recipient@example.com') } let(:recipient) { create(:user, email: 'recipient@example.com') }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:build) { create(:ci_build) }
before(:each) do before(:each) do
ActionMailer::Base.deliveries.clear ActionMailer::Base.deliveries.clear
...@@ -865,4 +866,32 @@ describe Notify do ...@@ -865,4 +866,32 @@ describe Notify do
is_expected.to have_body_text /#{diff_path}/ is_expected.to have_body_text /#{diff_path}/
end end
end end
describe 'build success' do
before { build.success }
subject { Notify.build_success_email(build.id, 'wow@example.com') }
it 'has the correct subject' do
should have_subject /Build success for/
end
it 'contains name of project' do
should have_body_text build.project_name
end
end
describe 'build fail' do
before { build.drop }
subject { Notify.build_fail_email(build.id, 'wow@example.com') }
it 'has the correct subject' do
should have_subject /Build failed for/
end
it 'contains name of project' do
should have_body_text build.project_name
end
end
end end
...@@ -73,26 +73,4 @@ describe ApplicationSetting, models: true do ...@@ -73,26 +73,4 @@ describe ApplicationSetting, models: true do
expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com']) expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com'])
end end
end end
context 'shared runners' do
let(:gl_project) { create(:empty_project) }
before do
allow_any_instance_of(Project).to receive(:current_application_settings).and_return(setting)
end
subject { gl_project.ensure_gitlab_ci_project.shared_runners_enabled }
context 'enabled' do
before { setting.update_attributes(shared_runners_enabled: true) }
it { is_expected.to be_truthy }
end
context 'disabled' do
before { setting.update_attributes(shared_runners_enabled: false) }
it { is_expected.to be_falsey }
end
end
end end
...@@ -26,9 +26,8 @@ ...@@ -26,9 +26,8 @@
require 'spec_helper' require 'spec_helper'
describe Ci::Build, models: true do describe Ci::Build, models: true do
let(:project) { FactoryGirl.create :ci_project } let(:project) { FactoryGirl.create :empty_project }
let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project } let(:commit) { FactoryGirl.create :ci_commit, project: project }
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
let(:build) { FactoryGirl.create :ci_build, commit: commit } let(:build) { FactoryGirl.create :ci_build, commit: commit }
it { is_expected.to validate_presence_of :ref } it { is_expected.to validate_presence_of :ref }
...@@ -112,7 +111,7 @@ describe Ci::Build, models: true do ...@@ -112,7 +111,7 @@ describe Ci::Build, models: true do
let(:token) { 'my_secret_token' } let(:token) { 'my_secret_token' }
before do before do
build.project.update_attributes(token: token) build.project.update_attributes(runners_token: token)
build.update_attributes(trace: token) build.update_attributes(trace: token)
end end
...@@ -120,11 +119,12 @@ describe Ci::Build, models: true do ...@@ -120,11 +119,12 @@ describe Ci::Build, models: true do
end end
end end
describe :timeout do # TODO: build timeout
subject { build.timeout } # describe :timeout do
# subject { build.timeout }
it { is_expected.to eq(commit.project.timeout) } #
end # it { is_expected.to eq(commit.project.timeout) }
# end
describe :options do describe :options do
let(:options) do let(:options) do
...@@ -140,11 +140,12 @@ describe Ci::Build, models: true do ...@@ -140,11 +140,12 @@ describe Ci::Build, models: true do
it { is_expected.to eq(options) } it { is_expected.to eq(options) }
end end
describe :allow_git_fetch do # TODO: allow_git_fetch
subject { build.allow_git_fetch } # describe :allow_git_fetch do
# subject { build.allow_git_fetch }
it { is_expected.to eq(project.allow_git_fetch) } #
end # it { is_expected.to eq(project.allow_git_fetch) }
# end
describe :project do describe :project do
subject { build.project } subject { build.project }
...@@ -164,12 +165,6 @@ describe Ci::Build, models: true do ...@@ -164,12 +165,6 @@ describe Ci::Build, models: true do
it { is_expected.to eq(project.name) } it { is_expected.to eq(project.name) }
end end
describe :repo_url do
subject { build.repo_url }
it { is_expected.to eq(project.repo_url_with_auth) }
end
describe :extract_coverage do describe :extract_coverage do
context 'valid content & regex' do context 'valid content & regex' do
subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') } subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') }
...@@ -266,40 +261,6 @@ describe Ci::Build, models: true do ...@@ -266,40 +261,6 @@ describe Ci::Build, models: true do
end end
end end
describe :project_recipients do
let(:pusher_email) { 'pusher@gitlab.test' }
let(:user) { User.new(notification_email: pusher_email) }
subject { build.project_recipients }
before do
build.update_attributes(user: user)
end
it 'should return pusher_email as only recipient when no additional recipients are given' do
project.update_attributes(email_add_pusher: true,
email_recipients: '')
is_expected.to eq([pusher_email])
end
it 'should return pusher_email and additional recipients' do
project.update_attributes(email_add_pusher: true,
email_recipients: 'rec1 rec2')
is_expected.to eq(['rec1', 'rec2', pusher_email])
end
it 'should return recipients' do
project.update_attributes(email_add_pusher: false,
email_recipients: 'rec1 rec2')
is_expected.to eq(['rec1', 'rec2'])
end
it 'should return unique recipients only' do
project.update_attributes(email_add_pusher: true,
email_recipients: "rec1 rec1 #{pusher_email}")
is_expected.to eq(['rec1', pusher_email])
end
end
describe :can_be_served? do describe :can_be_served? do
let(:runner) { FactoryGirl.create :ci_specific_runner } let(:runner) { FactoryGirl.create :ci_specific_runner }
...@@ -415,4 +376,18 @@ describe Ci::Build, models: true do ...@@ -415,4 +376,18 @@ describe Ci::Build, models: true do
is_expected.to_not be_nil is_expected.to_not be_nil
end end
end end
describe :repo_url do
let(:build) { FactoryGirl.create :ci_build }
let(:project) { build.project }
subject { build.repo_url }
it { is_expected.to be_a(String) }
it { is_expected.to end_with(".git") }
it { is_expected.to start_with(project.web_url[0..6]) }
it { is_expected.to include(build.token) }
it { is_expected.to include('gitlab-ci-token') }
it { is_expected.to include(project.web_url[7..-1]) }
end
end end
...@@ -13,17 +13,16 @@ ...@@ -13,17 +13,16 @@
# tag :boolean default(FALSE) # tag :boolean default(FALSE)
# yaml_errors :text # yaml_errors :text
# committed_at :datetime # committed_at :datetime
# gl_project_id :integer # project_id :integer
# #
require 'spec_helper' require 'spec_helper'
describe Ci::Commit, models: true do describe Ci::Commit, models: true do
let(:project) { FactoryGirl.create :ci_project } let(:project) { FactoryGirl.create :empty_project }
let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project } let(:commit) { FactoryGirl.create :ci_commit, project: project }
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
it { is_expected.to belong_to(:gl_project) } it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:statuses) } it { is_expected.to have_many(:statuses) }
it { is_expected.to have_many(:trigger_requests) } it { is_expected.to have_many(:trigger_requests) }
it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:builds) }
...@@ -37,16 +36,16 @@ describe Ci::Commit, models: true do ...@@ -37,16 +36,16 @@ describe Ci::Commit, models: true do
let(:project) { FactoryGirl.create :empty_project } let(:project) { FactoryGirl.create :empty_project }
it 'returns ordered list of commits' do it 'returns ordered list of commits' do
commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, project: project
expect(project.ci_commits.ordered).to eq([commit2, commit1]) expect(project.ci_commits.ordered).to eq([commit2, commit1])
end end
it 'returns commits ordered by committed_at and id, with nulls last' do it 'returns commits ordered by committed_at and id, with nulls last' do
commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
commit2 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project commit2 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, project: project
commit4 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project commit4 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
expect(project.ci_commits.ordered).to eq([commit2, commit4, commit3, commit1]) expect(project.ci_commits.ordered).to eq([commit2, commit4, commit3, commit1])
end end
end end
...@@ -162,7 +161,7 @@ describe Ci::Commit, models: true do ...@@ -162,7 +161,7 @@ describe Ci::Commit, models: true do
end end
describe :create_builds do describe :create_builds do
let!(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } let!(:commit) { FactoryGirl.create :ci_commit, project: project }
def create_builds(trigger_request = nil) def create_builds(trigger_request = nil)
commit.create_builds('master', false, nil, trigger_request) commit.create_builds('master', false, nil, trigger_request)
...@@ -390,9 +389,8 @@ describe Ci::Commit, models: true do ...@@ -390,9 +389,8 @@ describe Ci::Commit, models: true do
end end
describe "coverage" do describe "coverage" do
let(:project) { FactoryGirl.create :ci_project, coverage_regex: "/.*/" } let(:project) { FactoryGirl.create :empty_project, build_coverage_regex: "/.*/" }
let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project } let(:commit) { FactoryGirl.create :ci_commit, project: project }
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
it "calculates average when there are two builds with coverage" do it "calculates average when there are two builds with coverage" do
FactoryGirl.create :ci_build, name: "rspec", coverage: 30, commit: commit FactoryGirl.create :ci_build, name: "rspec", coverage: 30, commit: commit
......
require 'spec_helper'
describe Ci::HipChatMessage, models: true do
subject { Ci::HipChatMessage.new(build) }
let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) }
let(:build) do
commit.builds.first
end
context 'when all matrix builds succeed' do
it 'returns a successful message' do
commit.create_builds('master', false, nil)
commit.builds.update_all(status: "success")
commit.reload
expect(subject.status_color).to eq 'green'
expect(subject.notify?).to be_falsey
expect(subject.to_s).to match(/Commit #\d+/)
expect(subject.to_s).to match(/Successful in \d+ second\(s\)\./)
end
end
context 'when at least one matrix build fails' do
it 'returns a failure message' do
commit.create_builds('master', false, nil)
first_build = commit.builds.first
second_build = commit.builds.last
first_build.update(status: "success")
second_build.update(status: "failed")
expect(subject.status_color).to eq 'red'
expect(subject.notify?).to be_truthy
expect(subject.to_s).to match(/Commit #\d+/)
expect(subject.to_s).to match(/Failed in \d+ second\(s\)\./)
end
end
end
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
require 'spec_helper'
describe Ci::HipChatService, models: true do
describe "Validations" do
context "active" do
before do
subject.active = true
end
it { is_expected.to validate_presence_of :hipchat_room }
it { is_expected.to validate_presence_of :hipchat_token }
end
end
describe "Execute" do
let(:service) { Ci::HipChatService.new }
let(:commit) { FactoryGirl.create :ci_commit }
let(:build) { FactoryGirl.create :ci_build, commit: commit, status: 'failed' }
let(:api_url) { 'https://api.hipchat.com/v2/room/123/notification?auth_token=a1b2c3d4e5f6' }
before do
allow(service).to receive_messages(
project: commit.project,
project_id: commit.project_id,
notify_only_broken_builds: false,
hipchat_room: 123,
hipchat_token: 'a1b2c3d4e5f6'
)
WebMock.stub_request(:post, api_url)
end
it "should call the HipChat API" do
service.execute(build)
Ci::HipChatNotifierWorker.drain
expect( WebMock ).to have_requested(:post, api_url).once
end
it "calls the worker with expected arguments" do
expect( Ci::HipChatNotifierWorker ).to receive(:perform_async) \
.with(an_instance_of(String), hash_including(
token: 'a1b2c3d4e5f6',
room: 123,
server: 'https://api.hipchat.com',
color: 'red',
notify: true
))
service.execute(build)
end
end
end
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
require 'spec_helper'
describe Ci::MailService, models: true do
describe "Associations" do
it { is_expected.to belong_to :project }
end
describe "Validations" do
context "active" do
before do
subject.active = true
end
end
end
describe 'Sends email for' do
let(:mail) { Ci::MailService.new }
let(:user) { User.new(notification_email: 'git@example.com')}
describe 'failed build' do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true) }
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: 'failed', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
project: project
)
end
it do
perform_enqueued_jobs do
expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
expect(ActionMailer::Base.deliveries.last.to).to eq(["git@example.com"])
end
end
end
describe 'successfull build' do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true, email_only_broken_builds: false) }
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
project: project
)
end
it do
perform_enqueued_jobs do
expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
expect(ActionMailer::Base.deliveries.last.to).to eq(["git@example.com"])
end
end
end
describe 'successfull build and project has email_recipients' do
let(:project) do
FactoryGirl.create(:ci_project,
email_add_pusher: true,
email_only_broken_builds: false,
email_recipients: "jeroen@example.com")
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
project: project
)
end
it do
perform_enqueued_jobs do
expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(2)
expect(
ActionMailer::Base.deliveries.map(&:to).flatten
).to include("git@example.com", "jeroen@example.com")
end
end
end
describe 'successful build and notify only broken builds' do
let(:project) do
FactoryGirl.create(:ci_project,
email_add_pusher: true,
email_only_broken_builds: true,
email_recipients: "jeroen@example.com")
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
project: project
)
end
it do
perform_enqueued_jobs do
expect do
mail.execute(build) if mail.can_execute?(build)
end.to_not change{ ActionMailer::Base.deliveries.size }
end
end
end
describe 'successful build and can test service' do
let(:project) do
FactoryGirl.create(:ci_project,
email_add_pusher: true,
email_only_broken_builds: false,
email_recipients: "jeroen@example.com")
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
project: project
)
build
end
it do
expect(mail.can_test?).to eq(true)
end
end
describe 'retried build should not receive email' do
let(:project) do
FactoryGirl.create(:ci_project,
email_add_pusher: true,
email_only_broken_builds: true,
email_recipients: "jeroen@example.com")
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: 'failed', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
project: project
)
end
it do
Ci::Build.retry(build)
perform_enqueued_jobs do
expect do
mail.execute(build) if mail.can_execute?(build)
end.to_not change{ ActionMailer::Base.deliveries.size }
end
end
end
end
end
require 'spec_helper'
describe Ci::SlackMessage, models: true do
subject { Ci::SlackMessage.new(commit) }
let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) }
context 'when all matrix builds succeeded' do
let(:color) { 'good' }
it 'returns a message with success' do
commit.create_builds('master', false, nil)
commit.builds.update_all(status: "success")
commit.reload
expect(subject.color).to eq(color)
expect(subject.fallback).to include('Commit')
expect(subject.fallback).to include("\##{commit.id}")
expect(subject.fallback).to include('succeeded')
expect(subject.attachments.first[:fields]).to be_empty
end
end
context 'when one of matrix builds failed' do
let(:color) { 'danger' }
it 'returns a message with information about failed build' do
commit.create_builds('master', false, nil)
first_build = commit.builds.first
second_build = commit.builds.last
first_build.update(status: "success")
second_build.update(status: "failed")
expect(subject.color).to eq(color)
expect(subject.fallback).to include('Commit')
expect(subject.fallback).to include("\##{commit.id}")
expect(subject.fallback).to include('failed')
expect(subject.attachments.first[:fields].size).to eq(1)
expect(subject.attachments.first[:fields].first[:title]).to eq(second_build.name)
expect(subject.attachments.first[:fields].first[:value]).to include("\##{second_build.id}")
end
end
end
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
require 'spec_helper'
describe Ci::SlackService, models: true do
describe "Associations" do
it { is_expected.to belong_to :project }
end
describe "Validations" do
context "active" do
before do
subject.active = true
end
it { is_expected.to validate_presence_of :webhook }
end
end
describe "Execute" do
let(:slack) { Ci::SlackService.new }
let(:commit) { FactoryGirl.create :ci_commit }
let(:build) { FactoryGirl.create :ci_build, commit: commit, status: 'failed' }
let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
let(:notify_only_broken_builds) { false }
before do
allow(slack).to receive_messages(
project: commit.project,
project_id: commit.project_id,
webhook: webhook_url,
notify_only_broken_builds: notify_only_broken_builds
)
WebMock.stub_request(:post, webhook_url)
end
it "should call Slack API" do
slack.execute(build)
Ci::SlackNotifierWorker.drain
expect(WebMock).to have_requested(:post, webhook_url).once
end
end
end
# == Schema Information
#
# Table name: ci_projects
#
# id :integer not null, primary key
# name :string(255)
# timeout :integer default(3600), not null
# created_at :datetime
# updated_at :datetime
# token :string(255)
# default_ref :string(255)
# path :string(255)
# always_build :boolean default(FALSE), not null
# polling_interval :integer
# public :boolean default(FALSE), not null
# ssh_url_to_repo :string(255)
# gitlab_id :integer
# allow_git_fetch :boolean default(TRUE), not null
# email_recipients :string(255) default(""), not null
# email_add_pusher :boolean default(TRUE), not null
# email_only_broken_builds :boolean default(TRUE), not null
# skip_refs :string(255)
# coverage_regex :string(255)
# shared_runners_enabled :boolean default(FALSE)
# generated_yaml_config :text
#
require 'spec_helper'
describe Ci::Project, models: true do
let(:project) { FactoryGirl.create :ci_project }
let(:gl_project) { project.gl_project }
subject { project }
it { is_expected.to have_many(:runner_projects) }
it { is_expected.to have_many(:runners) }
it { is_expected.to have_many(:web_hooks) }
it { is_expected.to have_many(:events) }
it { is_expected.to have_many(:variables) }
it { is_expected.to have_many(:triggers) }
it { is_expected.to have_many(:services) }
it { is_expected.to validate_presence_of :timeout }
it { is_expected.to validate_presence_of :gitlab_id }
describe 'before_validation' do
it 'should set an random token if none provided' do
project = FactoryGirl.create :ci_project_without_token
expect(project.token).not_to eq("")
end
it 'should not set an random toke if one provided' do
project = FactoryGirl.create :ci_project
expect(project.token).to eq("iPWx6WM4lhHNedGfBpPJNP")
end
end
describe :name_with_namespace do
subject { project.name_with_namespace }
it { is_expected.to eq(project.name) }
it { is_expected.to eq(gl_project.name_with_namespace) }
end
describe :path_with_namespace do
subject { project.path_with_namespace }
it { is_expected.to eq(project.path) }
it { is_expected.to eq(gl_project.path_with_namespace) }
end
describe :path_with_namespace do
subject { project.web_url }
it { is_expected.to eq(gl_project.web_url) }
end
describe :web_url do
subject { project.web_url }
it { is_expected.to eq(project.gitlab_url) }
it { is_expected.to eq(gl_project.web_url) }
end
describe :http_url_to_repo do
subject { project.http_url_to_repo }
it { is_expected.to eq(gl_project.http_url_to_repo) }
end
describe :ssh_url_to_repo do
subject { project.ssh_url_to_repo }
it { is_expected.to eq(gl_project.ssh_url_to_repo) }
end
describe :commits do
subject { project.commits }
before do
FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: gl_project
end
it { is_expected.to eq(gl_project.ci_commits) }
end
describe :builds do
subject { project.builds }
before do
commit = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: gl_project
FactoryGirl.create :ci_build, commit: commit
end
it { is_expected.to eq(gl_project.ci_builds) }
end
describe "ordered_by_last_commit_date" do
it "returns ordered projects" do
newest_project = FactoryGirl.create :empty_project
newest_ci_project = newest_project.ensure_gitlab_ci_project
oldest_project = FactoryGirl.create :empty_project
oldest_ci_project = oldest_project.ensure_gitlab_ci_project
project_without_commits = FactoryGirl.create :empty_project
ci_project_without_commits = project_without_commits.ensure_gitlab_ci_project
FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: newest_project
FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: oldest_project
expect(Ci::Project.ordered_by_last_commit_date).to eq([newest_ci_project, oldest_ci_project, ci_project_without_commits])
end
end
context :valid_project do
let(:commit) { FactoryGirl.create(:ci_commit) }
context :project_with_commit_and_builds do
let(:project) { commit.project }
before do
FactoryGirl.create(:ci_build, commit: commit)
end
it { expect(project.status).to eq('pending') }
it { expect(project.last_commit).to be_kind_of(Ci::Commit) }
it { expect(project.human_status).to eq('pending') }
end
end
describe '#email_notification?' do
it do
project = FactoryGirl.create :ci_project, email_add_pusher: true
expect(project.email_notification?).to eq(true)
end
it do
project = FactoryGirl.create :ci_project, email_add_pusher: false, email_recipients: 'test tesft'
expect(project.email_notification?).to eq(true)
end
it do
project = FactoryGirl.create :ci_project, email_add_pusher: false, email_recipients: ''
expect(project.email_notification?).to eq(false)
end
end
describe '#broken_or_success?' do
it do
project = FactoryGirl.create :ci_project, email_add_pusher: true
allow(project).to receive(:broken?).and_return(true)
allow(project).to receive(:success?).and_return(true)
expect(project.broken_or_success?).to eq(true)
end
it do
project = FactoryGirl.create :ci_project, email_add_pusher: true
allow(project).to receive(:broken?).and_return(true)
allow(project).to receive(:success?).and_return(false)
expect(project.broken_or_success?).to eq(true)
end
it do
project = FactoryGirl.create :ci_project, email_add_pusher: true
allow(project).to receive(:broken?).and_return(false)
allow(project).to receive(:success?).and_return(true)
expect(project.broken_or_success?).to eq(true)
end
it do
project = FactoryGirl.create :ci_project, email_add_pusher: true
allow(project).to receive(:broken?).and_return(false)
allow(project).to receive(:success?).and_return(false)
expect(project.broken_or_success?).to eq(false)
end
end
describe :repo_url_with_auth do
let(:project) { FactoryGirl.create :ci_project }
subject { project.repo_url_with_auth }
it { is_expected.to be_a(String) }
it { is_expected.to end_with(".git") }
it { is_expected.to start_with(project.gitlab_url[0..6]) }
it { is_expected.to include(project.token) }
it { is_expected.to include('gitlab-ci-token') }
it { is_expected.to include(project.gitlab_url[7..-1]) }
end
describe :any_runners do
it "there are no runners available" do
project = FactoryGirl.create(:ci_project)
expect(project.any_runners?).to be_falsey
end
it "there is a specific runner" do
project = FactoryGirl.create(:ci_project)
project.runners << FactoryGirl.create(:ci_specific_runner)
expect(project.any_runners?).to be_truthy
end
it "there is a shared runner" do
project = FactoryGirl.create(:ci_project, shared_runners_enabled: true)
FactoryGirl.create(:ci_shared_runner)
expect(project.any_runners?).to be_truthy
end
it "there is a shared runner, but they are prohibited to use" do
project = FactoryGirl.create(:ci_project)
FactoryGirl.create(:ci_shared_runner)
expect(project.any_runners?).to be_falsey
end
it "checks the presence of specific runner" do
project = FactoryGirl.create(:ci_project)
specific_runner = FactoryGirl.create(:ci_specific_runner)
project.runners << specific_runner
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
end
it "checks the presence of shared runner" do
project = FactoryGirl.create(:ci_project, shared_runners_enabled: true)
shared_runner = FactoryGirl.create(:ci_shared_runner)
expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
end
end
end
...@@ -38,7 +38,7 @@ describe Ci::Runner, models: true do ...@@ -38,7 +38,7 @@ describe Ci::Runner, models: true do
end end
describe :assign_to do describe :assign_to do
let!(:project) { FactoryGirl.create :ci_project } let!(:project) { FactoryGirl.create :empty_project }
let!(:shared_runner) { FactoryGirl.create(:ci_shared_runner) } let!(:shared_runner) { FactoryGirl.create(:ci_shared_runner) }
before { shared_runner.assign_to(project) } before { shared_runner.assign_to(project) }
...@@ -116,8 +116,8 @@ describe Ci::Runner, models: true do ...@@ -116,8 +116,8 @@ describe Ci::Runner, models: true do
describe "belongs_to_one_project?" do describe "belongs_to_one_project?" do
it "returns false if there are two projects runner assigned to" do it "returns false if there are two projects runner assigned to" do
runner = FactoryGirl.create(:ci_specific_runner) runner = FactoryGirl.create(:ci_specific_runner)
project = FactoryGirl.create(:ci_project) project = FactoryGirl.create(:empty_project)
project1 = FactoryGirl.create(:ci_project) project1 = FactoryGirl.create(:empty_project)
project.runners << runner project.runners << runner
project1.runners << runner project1.runners << runner
...@@ -126,7 +126,7 @@ describe Ci::Runner, models: true do ...@@ -126,7 +126,7 @@ describe Ci::Runner, models: true do
it "returns true" do it "returns true" do
runner = FactoryGirl.create(:ci_specific_runner) runner = FactoryGirl.create(:ci_specific_runner)
project = FactoryGirl.create(:ci_project) project = FactoryGirl.create(:empty_project)
project.runners << runner project.runners << runner
expect(runner.belongs_to_one_project?).to be_truthy expect(runner.belongs_to_one_project?).to be_truthy
......
# == Schema Information
#
# Table name: ci_services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
require 'spec_helper'
describe Ci::Service, models: true do
describe "Associations" do
it { is_expected.to belong_to :project }
end
describe "Mass assignment" do
end
describe "Test Button" do
before do
@service = Ci::Service.new
end
describe "Testable" do
let(:commit) { FactoryGirl.create :ci_commit }
let(:build) { FactoryGirl.create :ci_build, commit: commit }
before do
allow(@service).to receive_messages(
project: commit.project
)
build
@testable = @service.can_test?
end
describe :can_test do
it { expect(@testable).to eq(true) }
end
end
end
end
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
require 'spec_helper' require 'spec_helper'
describe Ci::Trigger, models: true do describe Ci::Trigger, models: true do
let(:project) { FactoryGirl.create :ci_project } let(:project) { FactoryGirl.create :empty_project }
describe 'before_validation' do describe 'before_validation' do
it 'should set an random token if none provided' do it 'should set an random token if none provided' do
......
# == Schema Information
#
# Table name: ci_web_hooks
#
# id :integer not null, primary key
# url :string(255) not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
#
require 'spec_helper'
describe Ci::WebHook, models: true do
describe "Associations" do
it { is_expected.to belong_to :project }
end
describe "Validations" do
it { is_expected.to validate_presence_of(:url) }
context "url format" do
it { is_expected.to allow_value("http://example.com").for(:url) }
it { is_expected.to allow_value("https://excample.com").for(:url) }
it { is_expected.to allow_value("http://test.com/api").for(:url) }
it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) }
it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) }
it { is_expected.not_to allow_value("example.com").for(:url) }
it { is_expected.not_to allow_value("ftp://example.com").for(:url) }
it { is_expected.not_to allow_value("herp-and-derp").for(:url) }
end
end
describe "execute" do
before(:each) do
@web_hook = FactoryGirl.create(:ci_web_hook)
@project = @web_hook.project
@data = { before: 'oldrev', after: 'newrev', ref: 'ref' }
WebMock.stub_request(:post, @web_hook.url)
end
it "POSTs to the web hook URL" do
@web_hook.execute(@data)
expect(WebMock).to have_requested(:post, @web_hook.url).once
end
it "POSTs the data as JSON" do
json = @data.to_json
@web_hook.execute(@data)
expect(WebMock).to have_requested(:post, @web_hook.url).with(body: json).once
end
it "catches exceptions" do
expect(Ci::WebHook).to receive(:post).and_raise("Some HTTP Post error")
expect{ @web_hook.execute(@data) }.
to raise_error(RuntimeError, 'Some HTTP Post error')
end
end
end
...@@ -39,12 +39,13 @@ describe CommitStatus, models: true do ...@@ -39,12 +39,13 @@ describe CommitStatus, models: true do
it { is_expected.to belong_to(:commit) } it { is_expected.to belong_to(:commit) }
it { is_expected.to belong_to(:user) } it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:project) }
it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_inclusion_of(:status).in_array(%w(pending running failed success canceled)) } it { is_expected.to validate_inclusion_of(:status).in_array(%w(pending running failed success canceled)) }
it { is_expected.to delegate_method(:sha).to(:commit) } it { is_expected.to delegate_method(:sha).to(:commit) }
it { is_expected.to delegate_method(:short_sha).to(:commit) } it { is_expected.to delegate_method(:short_sha).to(:commit) }
it { is_expected.to delegate_method(:gl_project).to(:commit) }
it { is_expected.to respond_to :success? } it { is_expected.to respond_to :success? }
it { is_expected.to respond_to :failed? } it { is_expected.to respond_to :failed? }
......
...@@ -247,6 +247,55 @@ describe HipchatService, models: true do ...@@ -247,6 +247,55 @@ describe HipchatService, models: true do
end end
end end
context 'build events' do
let(:build) { create(:ci_build) }
let(:data) { Gitlab::BuildDataBuilder.build(build) }
context 'for failed' do
before { build.drop }
it "should call Hipchat API" do
hipchat.execute(data)
expect(WebMock).to have_requested(:post, api_url).once
end
it "should create a build message" do
message = hipchat.send(:create_build_message, data)
project_url = project.web_url
project_name = project.name_with_namespace.gsub(/\s/, '')
sha = data[:sha]
ref = data[:ref]
ref_type = data[:tag] ? 'tag' : 'branch'
duration = data[:commit][:duration]
expect(message).to eq("<a href=\"#{project_url}\">#{project_name}</a>: " \
"Commit <a href=\"#{project_url}/commit/#{sha}/builds\">#{Commit.truncate_sha(sha)}</a> " \
"of <a href=\"#{project_url}/commits/#{ref}\">#{ref}</a> #{ref_type} " \
"by #{data[:commit][:author_name]} failed in #{duration} second(s)")
end
end
context 'for succeeded' do
before do
build.success
end
it "should call Hipchat API" do
hipchat.notify_only_broken_builds = false
hipchat.execute(data)
expect(WebMock).to have_requested(:post, api_url).once
end
it "should notify only broken" do
hipchat.notify_only_broken_builds = true
hipchat.execute(data)
expect(WebMock).to_not have_requested(:post, api_url).once
end
end
end
context "#message_options" do context "#message_options" do
it "should be set to the defaults" do it "should be set to the defaults" do
expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'yellow' }) expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'yellow' })
......
require 'spec_helper'
describe SlackService::BuildMessage do
subject { SlackService::BuildMessage.new(args) }
let(:args) do
{
sha: '97de212e80737a608d939f648d959671fb0a0142',
ref: 'develop',
tag: false,
project_name: 'project_name',
project_url: 'somewhere.com',
commit: {
status: status,
author_name: 'hacker',
duration: 10,
},
}
end
context 'succeeded' do
let(:status) { 'success' }
let(:color) { 'good' }
it 'returns a message with information about succeeded build' do
message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker passed in 10 second(s)'
expect(subject.pretext).to be_empty
expect(subject.fallback).to eq(message)
expect(subject.attachments).to eq([text: message, color: color])
end
end
context 'failed' do
let(:status) { 'failed' }
let(:color) { 'danger' }
it 'returns a message with information about failed build' do
message = '<somewhere.com|project_name>: Commit <somewhere.com/commit/97de212e80737a608d939f648d959671fb0a0142/builds|97de212e> of <somewhere.com/commits/develop|develop> branch by hacker failed in 10 second(s)'
expect(subject.pretext).to be_empty
expect(subject.fallback).to eq(message)
expect(subject.attachments).to eq([text: message, color: color])
end
end
end
...@@ -54,6 +54,13 @@ describe Project, models: true do ...@@ -54,6 +54,13 @@ describe Project, models: true do
it { is_expected.to have_one(:slack_service).dependent(:destroy) } it { is_expected.to have_one(:slack_service).dependent(:destroy) }
it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
it { is_expected.to have_one(:asana_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) }
it { is_expected.to have_many(:commit_statuses) }
it { is_expected.to have_many(:ci_commits) }
it { is_expected.to have_many(:builds) }
it { is_expected.to have_many(:runner_projects) }
it { is_expected.to have_many(:runners) }
it { is_expected.to have_many(:variables) }
it { is_expected.to have_many(:triggers) }
end end
describe 'modules' do describe 'modules' do
...@@ -88,6 +95,18 @@ describe Project, models: true do ...@@ -88,6 +95,18 @@ describe Project, models: true do
expect(project2.errors[:limit_reached].first).to match(/Your project limit is 0/) expect(project2.errors[:limit_reached].first).to match(/Your project limit is 0/)
end end
end end
describe 'project token' do
it 'should set an random token if none provided' do
project = FactoryGirl.create :empty_project, runners_token: ''
expect(project.runners_token).not_to eq('')
end
it 'should not set an random toke if one provided' do
project = FactoryGirl.create :empty_project, runners_token: 'my-token'
expect(project.runners_token).to eq('my-token')
end
end
describe 'Respond to' do describe 'Respond to' do
it { is_expected.to respond_to(:url_to_repo) } it { is_expected.to respond_to(:url_to_repo) }
...@@ -395,12 +414,7 @@ describe Project, models: true do ...@@ -395,12 +414,7 @@ describe Project, models: true do
describe :ci_commit do describe :ci_commit do
let(:project) { create :project } let(:project) { create :project }
let(:commit) { create :ci_commit, gl_project: project } let(:commit) { create :ci_commit, project: project }
before do
project.ensure_gitlab_ci_project
project.create_gitlab_ci_service(active: true)
end
it { expect(project.ci_commit(commit.sha)).to eq(commit) } it { expect(project.ci_commit(commit.sha)).to eq(commit) }
end end
...@@ -412,9 +426,7 @@ describe Project, models: true do ...@@ -412,9 +426,7 @@ describe Project, models: true do
subject { project.builds_enabled } subject { project.builds_enabled }
it { is_expected.to eq(project.gitlab_ci_service.active) }
it { expect(project.builds_enabled?).to be_truthy } it { expect(project.builds_enabled?).to be_truthy }
it { expect(project.gitlab_ci_project).to be_a(Ci::Project) }
end end
describe '.trending' do describe '.trending' do
...@@ -475,4 +487,65 @@ describe Project, models: true do ...@@ -475,4 +487,65 @@ describe Project, models: true do
it { is_expected.to eq([]) } it { is_expected.to eq([]) }
end end
end end
context 'shared runners by default' do
let(:project) { create(:empty_project) }
subject { project.shared_runners_enabled }
context 'are enabled' do
before { stub_application_setting(shared_runners_enabled: true) }
it { is_expected.to be_truthy }
end
context 'are disabled' do
before { stub_application_setting(shared_runners_enabled: false) }
it { is_expected.to be_falsey }
end
end
describe :any_runners do
let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) }
let(:specific_runner) { create(:ci_specific_runner) }
let(:shared_runner) { create(:ci_shared_runner) }
context 'for shared runners disabled' do
let(:shared_runners_enabled) { false }
it 'there are no runners available' do
expect(project.any_runners?).to be_falsey
end
it 'there is a specific runner' do
project.runners << specific_runner
expect(project.any_runners?).to be_truthy
end
it 'there is a shared runner, but they are prohibited to use' do
shared_runner
expect(project.any_runners?).to be_falsey
end
it 'checks the presence of specific runner' do
project.runners << specific_runner
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
end
end
context 'for shared runners enabled' do
let(:shared_runners_enabled) { true }
it 'there is a shared runner' do
shared_runner
expect(project.any_runners?).to be_truthy
end
it 'checks the presence of shared runner' do
shared_runner
expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
end
end
end
end end
require 'spec_helper' require 'spec_helper'
describe API::API, 'ProjectHooks', api: true do describe API::API, 'ProjectHooks', api: true do
include ApiHelpers include ApiHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user3) { create(:user) } let(:user3) { create(:user) }
let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:hook) { create(:project_hook, project: project, url: "http://example.com", push_events: true, merge_requests_events: true, tag_push_events: true, issues_events: true, note_events: true, enable_ssl_verification: true) } let!(:hook) do
create(:project_hook,
project: project, url: "http://example.com",
push_events: true, merge_requests_events: true, tag_push_events: true,
issues_events: true, note_events: true, build_events: true,
enable_ssl_verification: true)
end
before do before do
project.team << [user, :master] project.team << [user, :master]
...@@ -26,6 +32,7 @@ describe API::API, 'ProjectHooks', api: true do ...@@ -26,6 +32,7 @@ describe API::API, 'ProjectHooks', api: true do
expect(json_response.first['merge_requests_events']).to eq(true) expect(json_response.first['merge_requests_events']).to eq(true)
expect(json_response.first['tag_push_events']).to eq(true) expect(json_response.first['tag_push_events']).to eq(true)
expect(json_response.first['note_events']).to eq(true) expect(json_response.first['note_events']).to eq(true)
expect(json_response.first['build_events']).to eq(true)
expect(json_response.first['enable_ssl_verification']).to eq(true) expect(json_response.first['enable_ssl_verification']).to eq(true)
end end
end end
...@@ -83,6 +90,7 @@ describe API::API, 'ProjectHooks', api: true do ...@@ -83,6 +90,7 @@ describe API::API, 'ProjectHooks', api: true do
expect(json_response['merge_requests_events']).to eq(false) expect(json_response['merge_requests_events']).to eq(false)
expect(json_response['tag_push_events']).to eq(false) expect(json_response['tag_push_events']).to eq(false)
expect(json_response['note_events']).to eq(false) expect(json_response['note_events']).to eq(false)
expect(json_response['build_events']).to eq(false)
expect(json_response['enable_ssl_verification']).to eq(true) expect(json_response['enable_ssl_verification']).to eq(true)
end end
......
...@@ -86,18 +86,6 @@ describe API::API, api: true do ...@@ -86,18 +86,6 @@ describe API::API, api: true do
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(project3.id) expect(json_response.first['id']).to eq(project3.id)
end end
it 'returns projects in the correct order when ci_enabled_first parameter is passed' do
[project, project2, project3].each do |project|
project.builds_enabled = false
project.build_missing_services
end
project2.builds_enabled = true
get api('/projects', user), { ci_enabled_first: 'true' }
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(project2.id)
end
end end
end end
end end
......
require 'spec_helper'
describe API::API do
include ApiHelpers
describe 'POST /projects/:project_id/trigger' do
let!(:trigger_token) { 'secure token' }
let!(:project) { FactoryGirl.create(:project) }
let!(:project2) { FactoryGirl.create(:empty_project) }
let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) }
let(:options) do
{
token: trigger_token
}
end
before do
stub_ci_commit_to_return_yaml_file
end
context 'Handles errors' do
it 'should return bad request if token is missing' do
post api("/projects/#{project.id}/trigger/builds"), ref: 'master'
expect(response.status).to eq(400)
end
it 'should return not found if project is not found' do
post api('/projects/0/trigger/builds'), options.merge(ref: 'master')
expect(response.status).to eq(404)
end
it 'should return unauthorized if token is for different project' do
post api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master')
expect(response.status).to eq(401)
end
end
context 'Have a commit' do
let(:commit) { project.ci_commits.last }
it 'should create builds' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master')
expect(response.status).to eq(201)
commit.builds.reload
expect(commit.builds.size).to eq(2)
end
it 'should return bad request with no builds created if there\'s no commit for that ref' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch')
expect(response.status).to eq(400)
expect(json_response['message']).to eq('No builds created')
end
context 'Validates variables' do
let(:variables) do
{ 'TRIGGER_KEY' => 'TRIGGER_VALUE' }
end
it 'should validate variables to be a hash' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master')
expect(response.status).to eq(400)
expect(json_response['message']).to eq('variables needs to be a hash')
end
it 'should validate variables needs to be a map of key-valued strings' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
expect(response.status).to eq(400)
expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
end
it 'create trigger request with variables' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master')
expect(response.status).to eq(201)
commit.builds.reload
expect(commit.builds.first.trigger_request.variables).to eq(variables)
end
end
end
end
end
...@@ -4,8 +4,7 @@ describe Ci::API::API do ...@@ -4,8 +4,7 @@ describe Ci::API::API do
include ApiHelpers include ApiHelpers
let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) } let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) }
let(:project) { FactoryGirl.create(:ci_project) } let(:project) { FactoryGirl.create(:empty_project) }
let(:gl_project) { project.gl_project }
before do before do
stub_ci_commit_to_return_yaml_file stub_ci_commit_to_return_yaml_file
...@@ -13,16 +12,15 @@ describe Ci::API::API do ...@@ -13,16 +12,15 @@ describe Ci::API::API do
describe "Builds API for runners" do describe "Builds API for runners" do
let(:shared_runner) { FactoryGirl.create(:ci_runner, token: "SharedRunner") } let(:shared_runner) { FactoryGirl.create(:ci_runner, token: "SharedRunner") }
let(:shared_project) { FactoryGirl.create(:ci_project, name: "SharedProject") } let(:shared_project) { FactoryGirl.create(:empty_project, name: "SharedProject") }
let(:shared_gl_project) { shared_project.gl_project }
before do before do
FactoryGirl.create :ci_runner_project, project_id: project.id, runner_id: runner.id FactoryGirl.create :ci_runner_project, project: project, runner: runner
end end
describe "POST /builds/register" do describe "POST /builds/register" do
it "should start a build" do it "should start a build" do
commit = FactoryGirl.create(:ci_commit, gl_project: gl_project) commit = FactoryGirl.create(:ci_commit, project: project)
commit.create_builds('master', false, nil) commit.create_builds('master', false, nil)
build = commit.builds.first build = commit.builds.first
...@@ -40,7 +38,7 @@ describe Ci::API::API do ...@@ -40,7 +38,7 @@ describe Ci::API::API do
end end
it "should return 404 error if no builds for specific runner" do it "should return 404 error if no builds for specific runner" do
commit = FactoryGirl.create(:ci_commit, gl_project: shared_gl_project) commit = FactoryGirl.create(:ci_commit, project: shared_project)
FactoryGirl.create(:ci_build, commit: commit, status: 'pending') FactoryGirl.create(:ci_build, commit: commit, status: 'pending')
post ci_api("/builds/register"), token: runner.token post ci_api("/builds/register"), token: runner.token
...@@ -49,7 +47,7 @@ describe Ci::API::API do ...@@ -49,7 +47,7 @@ describe Ci::API::API do
end end
it "should return 404 error if no builds for shared runner" do it "should return 404 error if no builds for shared runner" do
commit = FactoryGirl.create(:ci_commit, gl_project: gl_project) commit = FactoryGirl.create(:ci_commit, project: project)
FactoryGirl.create(:ci_build, commit: commit, status: 'pending') FactoryGirl.create(:ci_build, commit: commit, status: 'pending')
post ci_api("/builds/register"), token: shared_runner.token post ci_api("/builds/register"), token: shared_runner.token
...@@ -58,7 +56,7 @@ describe Ci::API::API do ...@@ -58,7 +56,7 @@ describe Ci::API::API do
end end
it "returns options" do it "returns options" do
commit = FactoryGirl.create(:ci_commit, gl_project: gl_project) commit = FactoryGirl.create(:ci_commit, project: project)
commit.create_builds('master', false, nil) commit.create_builds('master', false, nil)
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
...@@ -68,7 +66,7 @@ describe Ci::API::API do ...@@ -68,7 +66,7 @@ describe Ci::API::API do
end end
it "returns variables" do it "returns variables" do
commit = FactoryGirl.create(:ci_commit, gl_project: gl_project) commit = FactoryGirl.create(:ci_commit, project: project)
commit.create_builds('master', false, nil) commit.create_builds('master', false, nil)
project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value") project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
...@@ -85,7 +83,7 @@ describe Ci::API::API do ...@@ -85,7 +83,7 @@ describe Ci::API::API do
it "returns variables for triggers" do it "returns variables for triggers" do
trigger = FactoryGirl.create(:ci_trigger, project: project) trigger = FactoryGirl.create(:ci_trigger, project: project)
commit = FactoryGirl.create(:ci_commit, gl_project: gl_project) commit = FactoryGirl.create(:ci_commit, project: project)
trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger) trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger)
commit.create_builds('master', false, nil, trigger_request) commit.create_builds('master', false, nil, trigger_request)
...@@ -106,7 +104,7 @@ describe Ci::API::API do ...@@ -106,7 +104,7 @@ describe Ci::API::API do
end end
describe "PUT /builds/:id" do describe "PUT /builds/:id" do
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project)} let(:commit) { FactoryGirl.create(:ci_commit, project: project)}
let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) } let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) }
it "should update a running build" do it "should update a running build" do
...@@ -126,14 +124,14 @@ describe Ci::API::API do ...@@ -126,14 +124,14 @@ describe Ci::API::API do
context "Artifacts" do context "Artifacts" do
let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') } let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) } let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) } let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) }
let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") } let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") }
let(:post_url) { ci_api("/builds/#{build.id}/artifacts") } let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") } let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
let(:get_url) { ci_api("/builds/#{build.id}/artifacts") } let(:get_url) { ci_api("/builds/#{build.id}/artifacts") }
let(:headers) { { "GitLab-Workhorse" => "1.0" } } let(:headers) { { "GitLab-Workhorse" => "1.0" } }
let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.project.token) } let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) }
describe "POST /builds/:id/artifacts/authorize" do describe "POST /builds/:id/artifacts/authorize" do
context "should authorize posting artifact to running build" do context "should authorize posting artifact to running build" do
...@@ -142,7 +140,7 @@ describe Ci::API::API do ...@@ -142,7 +140,7 @@ describe Ci::API::API do
end end
it "using token as parameter" do it "using token as parameter" do
post authorize_url, { token: build.project.token }, headers post authorize_url, { token: build.token }, headers
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response["TempPath"]).to_not be_nil expect(json_response["TempPath"]).to_not be_nil
end end
...@@ -161,7 +159,7 @@ describe Ci::API::API do ...@@ -161,7 +159,7 @@ describe Ci::API::API do
it "using token as parameter" do it "using token as parameter" do
stub_application_setting(max_artifacts_size: 0) stub_application_setting(max_artifacts_size: 0)
post authorize_url, { token: build.project.token, filesize: 100 }, headers post authorize_url, { token: build.token, filesize: 100 }, headers
expect(response.status).to eq(413) expect(response.status).to eq(413)
end end
...@@ -241,7 +239,7 @@ describe Ci::API::API do ...@@ -241,7 +239,7 @@ describe Ci::API::API do
end end
it do it do
post post_url, { token: build.project.token }, {} post post_url, { token: build.token }, {}
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
end end
...@@ -281,12 +279,12 @@ describe Ci::API::API do ...@@ -281,12 +279,12 @@ describe Ci::API::API do
describe "DELETE /builds/:id/artifacts" do describe "DELETE /builds/:id/artifacts" do
before do before do
build.run! build.run!
post delete_url, token: build.project.token, file: file_upload post delete_url, token: build.token, file: file_upload
end end
it "should delete artifact build" do it "should delete artifact build" do
build.success build.success
delete delete_url, token: build.project.token delete delete_url, token: build.token
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
end end
...@@ -298,12 +296,12 @@ describe Ci::API::API do ...@@ -298,12 +296,12 @@ describe Ci::API::API do
it "should download artifact" do it "should download artifact" do
build.update_attributes(artifacts_file: file_upload) build.update_attributes(artifacts_file: file_upload)
get get_url, token: build.project.token get get_url, token: build.token
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
it "should fail to download if no artifact uploaded" do it "should fail to download if no artifact uploaded" do
get get_url, token: build.project.token get get_url, token: build.token
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
end end
......
require 'spec_helper'
describe Ci::API::API, 'Commits' do
include ApiHelpers
let(:project) { FactoryGirl.create(:ci_project) }
let(:gl_project) { project.gl_project }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:options) do
{
project_token: project.token,
project_id: project.id
}
end
describe "GET /commits" do
before { commit }
it "should return commits per project" do
get ci_api("/commits"), options
expect(response.status).to eq(200)
expect(json_response.count).to eq(1)
expect(json_response.first["project_id"]).to eq(project.id)
expect(json_response.first["sha"]).to eq(commit.sha)
end
end
describe "POST /commits" do
let(:data) do
{
"before" => "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after" => "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"ref" => "refs/heads/master",
"commits" => [
{
"id" => "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"message" => "Update Catalan translation to e38cb41.",
"timestamp" => "2011-12-12T14:27:31+02:00",
"url" => "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"author" => {
"name" => "Jordi Mallach",
"email" => "jordi@softcatala.org",
}
}
]
}
end
it "should create a build" do
post ci_api("/commits"), options.merge(data: data)
expect(response.status).to eq(201)
expect(json_response['sha']).to eq("da1560886d4f094c3e6c9ef40349f7d38b5d27d7")
end
it "should return 400 error if no data passed" do
post ci_api("/commits"), options
expect(response.status).to eq(400)
expect(json_response['message']).to eq("400 (Bad request) \"data\" not given")
end
end
end
require 'spec_helper'
describe Ci::API::API do
include ApiHelpers
let(:gitlab_url) { GitlabCi.config.gitlab_ci.url }
let(:user) { create(:user) }
let(:private_token) { user.private_token }
let(:options) do
{
private_token: private_token,
url: gitlab_url
}
end
before do
stub_gitlab_calls
end
context "requests for scoped projects" do
# NOTE: These ids are tied to the actual projects on demo.gitlab.com
describe "GET /projects" do
let!(:project1) { FactoryGirl.create(:ci_project) }
let!(:project2) { FactoryGirl.create(:ci_project) }
before do
project1.gl_project.team << [user, :developer]
project2.gl_project.team << [user, :developer]
end
it "should return all projects on the CI instance" do
get ci_api("/projects"), options
expect(response.status).to eq(200)
expect(json_response.count).to eq(2)
expect(json_response.first["id"]).to eq(project1.id)
expect(json_response.last["id"]).to eq(project2.id)
end
end
describe "GET /projects/owned" do
let!(:gl_project1) {FactoryGirl.create(:empty_project, namespace: user.namespace)}
let!(:gl_project2) {FactoryGirl.create(:empty_project, namespace: user.namespace)}
let!(:project1) { gl_project1.ensure_gitlab_ci_project }
let!(:project2) { gl_project2.ensure_gitlab_ci_project }
before do
project1.gl_project.team << [user, :developer]
project2.gl_project.team << [user, :developer]
end
it "should return all projects on the CI instance" do
get ci_api("/projects/owned"), options
expect(response.status).to eq(200)
expect(json_response.count).to eq(2)
end
end
end
describe "POST /projects/:project_id/webhooks" do
let!(:project) { FactoryGirl.create(:ci_project) }
context "Valid Webhook URL" do
let!(:webhook) { { web_hook: "http://example.com/sth/1/ala_ma_kota" } }
before do
options.merge!(webhook)
end
it "should create webhook for specified project" do
project.gl_project.team << [user, :master]
post ci_api("/projects/#{project.id}/webhooks"), options
expect(response.status).to eq(201)
expect(json_response["url"]).to eq(webhook[:web_hook])
end
it "fails to create webhook for non existsing project" do
post ci_api("/projects/non-existant-id/webhooks"), options
expect(response.status).to eq(404)
end
it "non-manager is not authorized" do
post ci_api("/projects/#{project.id}/webhooks"), options
expect(response.status).to eq(401)
end
end
context "Invalid Webhook URL" do
let!(:webhook) { { web_hook: "ala_ma_kota" } }
before do
options.merge!(webhook)
end
it "fails to create webhook for not valid url" do
project.gl_project.team << [user, :master]
post ci_api("/projects/#{project.id}/webhooks"), options
expect(response.status).to eq(400)
end
end
context "Missed web_hook parameter" do
it "fails to create webhook for not provided url" do
project.gl_project.team << [user, :master]
post ci_api("/projects/#{project.id}/webhooks"), options
expect(response.status).to eq(400)
end
end
end
describe "GET /projects/:id" do
let!(:project) { FactoryGirl.create(:ci_project) }
before do
project.gl_project.team << [user, :developer]
end
context "with an existing project" do
it "should retrieve the project info" do
get ci_api("/projects/#{project.id}"), options
expect(response.status).to eq(200)
expect(json_response['id']).to eq(project.id)
end
end
context "with a non-existing project" do
it "should return 404 error if project not found" do
get ci_api("/projects/non_existent_id"), options
expect(response.status).to eq(404)
end
end
end
describe "PUT /projects/:id" do
let!(:project) { FactoryGirl.create(:ci_project) }
let!(:project_info) { { default_ref: "develop" } }
before do
options.merge!(project_info)
end
it "should update a specific project's information" do
project.gl_project.team << [user, :master]
put ci_api("/projects/#{project.id}"), options
expect(response.status).to eq(200)
expect(json_response["default_ref"]).to eq(project_info[:default_ref])
end
it "fails to update a non-existing project" do
put ci_api("/projects/non-existant-id"), options
expect(response.status).to eq(404)
end
it "non-manager is not authorized" do
put ci_api("/projects/#{project.id}"), options
expect(response.status).to eq(401)
end
end
describe "DELETE /projects/:id" do
let!(:project) { FactoryGirl.create(:ci_project) }
it "should delete a specific project" do
project.gl_project.team << [user, :master]
delete ci_api("/projects/#{project.id}"), options
expect(response.status).to eq(200)
expect { project.reload }.
to raise_error(ActiveRecord::RecordNotFound)
end
it "non-manager is not authorized" do
delete ci_api("/projects/#{project.id}"), options
expect(response.status).to eq(401)
end
it "is getting not found error" do
delete ci_api("/projects/not-existing_id"), options
expect(response.status).to eq(404)
end
end
describe "POST /projects/:id/runners/:id" do
let(:project) { FactoryGirl.create(:ci_project) }
let(:runner) { FactoryGirl.create(:ci_runner) }
it "should add the project to the runner" do
project.gl_project.team << [user, :master]
post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options
expect(response.status).to eq(201)
project.reload
expect(project.runners.first.id).to eq(runner.id)
end
it "should fail if it tries to link a non-existing project or runner" do
post ci_api("/projects/#{project.id}/runners/non-existing"), options
expect(response.status).to eq(404)
post ci_api("/projects/non-existing/runners/#{runner.id}"), options
expect(response.status).to eq(404)
end
it "non-manager is not authorized" do
allow_any_instance_of(User).to receive(:can_manage_project?).and_return(false)
post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options
expect(response.status).to eq(401)
end
end
describe "DELETE /projects/:id/runners/:id" do
let(:project) { FactoryGirl.create(:ci_project) }
let(:runner) { FactoryGirl.create(:ci_runner) }
it "should remove the project from the runner" do
project.gl_project.team << [user, :master]
post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options
expect(project.runners).to be_present
delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options
expect(response.status).to eq(200)
project.reload
expect(project.runners).to be_empty
end
it "non-manager is not authorized" do
delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options
expect(response.status).to eq(401)
end
end
end
...@@ -8,29 +8,6 @@ describe Ci::API::API do ...@@ -8,29 +8,6 @@ describe Ci::API::API do
stub_gitlab_calls stub_gitlab_calls
end end
describe "GET /runners" do
let(:gitlab_url) { GitlabCi.config.gitlab_ci.url }
let(:private_token) { create(:user).private_token }
let(:options) do
{
private_token: private_token,
url: gitlab_url
}
end
before do
5.times { FactoryGirl.create(:ci_runner) }
end
it "should retrieve a list of all runners" do
get ci_api("/runners", nil), options
expect(response.status).to eq(200)
expect(json_response.count).to eq(5)
expect(json_response.last).to have_key("id")
expect(json_response.last).to have_key("token")
end
end
describe "POST /runners/register" do describe "POST /runners/register" do
describe "should create a runner if token provided" do describe "should create a runner if token provided" do
before { post ci_api("/runners/register"), token: GitlabCi::REGISTRATION_TOKEN } before { post ci_api("/runners/register"), token: GitlabCi::REGISTRATION_TOKEN }
...@@ -53,8 +30,8 @@ describe Ci::API::API do ...@@ -53,8 +30,8 @@ describe Ci::API::API do
end end
describe "should create a runner if project token provided" do describe "should create a runner if project token provided" do
let(:project) { FactoryGirl.create(:ci_project) } let(:project) { FactoryGirl.create(:empty_project) }
before { post ci_api("/runners/register"), token: project.token } before { post ci_api("/runners/register"), token: project.runners_token }
it { expect(response.status).to eq(201) } it { expect(response.status).to eq(201) }
it { expect(project.runners.size).to eq(1) } it { expect(project.runners.size).to eq(1) }
......
...@@ -5,9 +5,8 @@ describe Ci::API::API do ...@@ -5,9 +5,8 @@ describe Ci::API::API do
describe 'POST /projects/:project_id/refs/:ref/trigger' do describe 'POST /projects/:project_id/refs/:ref/trigger' do
let!(:trigger_token) { 'secure token' } let!(:trigger_token) { 'secure token' }
let!(:gl_project) { FactoryGirl.create(:project) } let!(:project) { FactoryGirl.create(:project, ci_id: 10) }
let!(:project) { gl_project.ensure_gitlab_ci_project } let!(:project2) { FactoryGirl.create(:empty_project, ci_id: 11) }
let!(:project2) { FactoryGirl.create(:ci_project) }
let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) } let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) }
let(:options) do let(:options) do
{ {
...@@ -21,7 +20,7 @@ describe Ci::API::API do ...@@ -21,7 +20,7 @@ describe Ci::API::API do
context 'Handles errors' do context 'Handles errors' do
it 'should return bad request if token is missing' do it 'should return bad request if token is missing' do
post ci_api("/projects/#{project.id}/refs/master/trigger") post ci_api("/projects/#{project.ci_id}/refs/master/trigger")
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
...@@ -31,23 +30,23 @@ describe Ci::API::API do ...@@ -31,23 +30,23 @@ describe Ci::API::API do
end end
it 'should return unauthorized if token is for different project' do it 'should return unauthorized if token is for different project' do
post ci_api("/projects/#{project2.id}/refs/master/trigger"), options post ci_api("/projects/#{project2.ci_id}/refs/master/trigger"), options
expect(response.status).to eq(401) expect(response.status).to eq(401)
end end
end end
context 'Have a commit' do context 'Have a commit' do
let(:commit) { project.commits.last } let(:commit) { project.ci_commits.last }
it 'should create builds' do it 'should create builds' do
post ci_api("/projects/#{project.id}/refs/master/trigger"), options post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options
expect(response.status).to eq(201) expect(response.status).to eq(201)
commit.builds.reload commit.builds.reload
expect(commit.builds.size).to eq(2) expect(commit.builds.size).to eq(2)
end end
it 'should return bad request with no builds created if there\'s no commit for that ref' do it 'should return bad request with no builds created if there\'s no commit for that ref' do
post ci_api("/projects/#{project.id}/refs/other-branch/trigger"), options post ci_api("/projects/#{project.ci_id}/refs/other-branch/trigger"), options
expect(response.status).to eq(400) expect(response.status).to eq(400)
expect(json_response['message']).to eq('No builds created') expect(json_response['message']).to eq('No builds created')
end end
...@@ -58,19 +57,19 @@ describe Ci::API::API do ...@@ -58,19 +57,19 @@ describe Ci::API::API do
end end
it 'should validate variables to be a hash' do it 'should validate variables to be a hash' do
post ci_api("/projects/#{project.id}/refs/master/trigger"), options.merge(variables: 'value') post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: 'value')
expect(response.status).to eq(400) expect(response.status).to eq(400)
expect(json_response['message']).to eq('variables needs to be a hash') expect(json_response['message']).to eq('variables needs to be a hash')
end end
it 'should validate variables needs to be a map of key-valued strings' do it 'should validate variables needs to be a map of key-valued strings' do
post ci_api("/projects/#{project.id}/refs/master/trigger"), options.merge(variables: { key: %w(1 2) }) post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: { key: %w(1 2) })
expect(response.status).to eq(400) expect(response.status).to eq(400)
expect(json_response['message']).to eq('variables needs to be a map of key-valued strings') expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
end end
it 'create trigger request with variables' do it 'create trigger request with variables' do
post ci_api("/projects/#{project.id}/refs/master/trigger"), options.merge(variables: variables) post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: variables)
expect(response.status).to eq(201) expect(response.status).to eq(201)
commit.builds.reload commit.builds.reload
expect(commit.builds.first.trigger_request.variables).to eq(variables) expect(commit.builds.first.trigger_request.variables).to eq(variables)
......
require 'spec_helper'
module Ci
describe CreateCommitService, services: true do
let(:service) { CreateCommitService.new }
let(:project) { FactoryGirl.create(:ci_project) }
let(:user) { nil }
before do
stub_ci_commit_to_return_yaml_file
end
describe :execute do
context 'valid params' do
let(:commit) do
service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
commits: [ { message: "Message" } ]
)
end
it { expect(commit).to be_kind_of(Commit) }
it { expect(commit).to be_valid }
it { expect(commit).to be_persisted }
it { expect(commit).to eq(project.commits.last) }
it { expect(commit.builds.first).to be_kind_of(Build) }
end
context "skip tag if there is no build for it" do
it "creates commit if there is appropriate job" do
result = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: [ { message: "Message" } ]
)
expect(result).to be_persisted
end
it "creates commit if there is no appropriate job but deploy job has right ref setting" do
config = YAML.dump({ deploy: { deploy: "ls", only: ["0_1"] } })
stub_ci_commit_yaml_file(config)
result = service.execute(project, user,
ref: 'refs/heads/0_1',
before: '00000000',
after: '31das312',
commits: [ { message: "Message" } ]
)
expect(result).to be_persisted
end
end
it 'skips commits without .gitlab-ci.yml' do
stub_ci_commit_yaml_file(nil)
result = service.execute(project, user,
ref: 'refs/heads/0_1',
before: '00000000',
after: '31das312',
commits: [ { message: 'Message' } ]
)
expect(result).to be_persisted
expect(result.builds.any?).to be_falsey
expect(result.status).to eq('skipped')
expect(result.yaml_errors).to be_nil
end
it 'skips commits if yaml is invalid' do
message = 'message'
allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
stub_ci_commit_yaml_file('invalid: file: file')
commits = [{ message: message }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq('failed')
expect(commit.yaml_errors).to_not be_nil
end
describe :ci_skip? do
let(:message) { "some message[ci skip]" }
before do
allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
end
it "skips builds creation if there is [ci skip] tag in commit message" do
commits = [{ message: message }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
end
it "does not skips builds creation if there is no [ci skip] tag in commit message" do
allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { "some message" }
commits = [{ message: "some message" }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.first.name).to eq("staging")
end
it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do
stub_ci_commit_yaml_file('invalid: file: fiile')
commits = [{ message: message }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
expect(commit.yaml_errors).to be_nil
end
end
it "skips build creation if there are already builds" do
allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { gitlab_ci_yaml }
commits = [{ message: "message" }]
commit = service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.count(:all)).to eq(2)
commit = service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.count(:all)).to eq(2)
end
it "creates commit with failed status if yaml is invalid" do
stub_ci_commit_yaml_file('invalid: file')
commits = [{ message: "some message" }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.status).to eq("failed")
expect(commit.builds.any?).to be false
end
end
end
end
...@@ -2,8 +2,7 @@ require 'spec_helper' ...@@ -2,8 +2,7 @@ require 'spec_helper'
describe Ci::CreateTriggerRequestService, services: true do describe Ci::CreateTriggerRequestService, services: true do
let(:service) { Ci::CreateTriggerRequestService.new } let(:service) { Ci::CreateTriggerRequestService.new }
let(:gl_project) { create(:project) } let(:project) { create(:project) }
let(:project) { gl_project.ensure_gitlab_ci_project }
let(:trigger) { create(:ci_trigger, project: project) } let(:trigger) { create(:ci_trigger, project: project) }
before do before do
...@@ -29,7 +28,7 @@ describe Ci::CreateTriggerRequestService, services: true do ...@@ -29,7 +28,7 @@ describe Ci::CreateTriggerRequestService, services: true do
before do before do
stub_ci_commit_yaml_file('{}') stub_ci_commit_yaml_file('{}')
FactoryGirl.create :ci_commit, gl_project: gl_project FactoryGirl.create :ci_commit, project: project
end end
it { expect(subject).to be_nil } it { expect(subject).to be_nil }
......
require 'spec_helper'
describe Ci::EventService, services: true do
let(:project) { FactoryGirl.create :ci_project }
let(:user) { double(username: "root", id: 1) }
before do
Event.destroy_all
end
describe :remove_project do
it "creates event" do
Ci::EventService.new.remove_project(user, project)
expect(Ci::Event.admin.last.description).to eq("Project \"#{project.name_with_namespace}\" has been removed by root")
end
end
describe :create_project do
it "creates event" do
Ci::EventService.new.create_project(user, project)
expect(Ci::Event.admin.last.description).to eq("Project \"#{project.name_with_namespace}\" has been created by root")
end
end
describe :change_project_settings do
it "creates event" do
Ci::EventService.new.change_project_settings(user, project)
expect(Ci::Event.last.description).to eq("User \"root\" updated projects settings")
end
end
end
...@@ -3,16 +3,16 @@ require 'spec_helper' ...@@ -3,16 +3,16 @@ require 'spec_helper'
module Ci module Ci
describe ImageForBuildService, services: true do describe ImageForBuildService, services: true do
let(:service) { ImageForBuildService.new } let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:ci_project) } let(:project) { FactoryGirl.create(:empty_project) }
let(:gl_project) { FactoryGirl.create(:project, gitlab_ci_project: project) } let(:commit_sha) { '01234567890123456789' }
let(:commit_sha) { gl_project.commit('master').sha } let(:commit) { project.ensure_ci_commit(commit_sha) }
let(:commit) { gl_project.ensure_ci_commit(commit_sha) }
let(:build) { FactoryGirl.create(:ci_build, commit: commit) } let(:build) { FactoryGirl.create(:ci_build, commit: commit) }
describe :execute do describe :execute do
before { build } before { build }
context 'branch name' do context 'branch name' do
before { allow(project).to receive(:commit).and_return(OpenStruct.new(sha: commit_sha)) }
before { build.run! } before { build.run! }
let(:image) { service.execute(project, ref: 'master') } let(:image) { service.execute(project, ref: 'master') }
......
...@@ -3,14 +3,14 @@ require 'spec_helper' ...@@ -3,14 +3,14 @@ require 'spec_helper'
module Ci module Ci
describe RegisterBuildService, services: true do describe RegisterBuildService, services: true do
let!(:service) { RegisterBuildService.new } let!(:service) { RegisterBuildService.new }
let!(:gl_project) { FactoryGirl.create :empty_project } let!(:project) { FactoryGirl.create :empty_project, shared_runners_enabled: false }
let!(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } let!(:commit) { FactoryGirl.create :ci_commit, project: project }
let!(:pending_build) { FactoryGirl.create :ci_build, commit: commit } let!(:pending_build) { FactoryGirl.create :ci_build, commit: commit }
let!(:shared_runner) { FactoryGirl.create(:ci_runner, is_shared: true) } let!(:shared_runner) { FactoryGirl.create(:ci_runner, is_shared: true) }
let!(:specific_runner) { FactoryGirl.create(:ci_runner, is_shared: false) } let!(:specific_runner) { FactoryGirl.create(:ci_runner, is_shared: false) }
before do before do
specific_runner.assign_to(gl_project.ensure_gitlab_ci_project) specific_runner.assign_to(project)
end end
describe :execute do describe :execute do
...@@ -47,7 +47,7 @@ module Ci ...@@ -47,7 +47,7 @@ module Ci
context 'allow shared runners' do context 'allow shared runners' do
before do before do
gl_project.gitlab_ci_project.update(shared_runners_enabled: true) project.update(shared_runners_enabled: true)
end end
context 'shared runner' do context 'shared runner' do
...@@ -71,7 +71,7 @@ module Ci ...@@ -71,7 +71,7 @@ module Ci
context 'disallow shared runners' do context 'disallow shared runners' do
before do before do
gl_project.gitlab_ci_project.update(shared_runners_enabled: false) project.update(shared_runners_enabled: false)
end end
context 'shared runner' do context 'shared runner' do
......
require 'spec_helper'
describe Ci::WebHookService, services: true do
let(:project) { FactoryGirl.create :ci_project }
let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
let(:build) { FactoryGirl.create :ci_build, commit: commit }
let(:hook) { FactoryGirl.create :ci_web_hook, project: project }
describe :execute do
it "should execute successfully" do
stub_request(:post, hook.url).to_return(status: 200)
expect(Ci::WebHookService.new.build_end(build)).to be_truthy
end
end
context 'build_data' do
it "contains all needed fields" do
expect(build_data(build)).to include(
:build_id,
:project_id,
:ref,
:build_status,
:build_started_at,
:build_finished_at,
:before_sha,
:project_name,
:gitlab_url,
:build_name
)
end
end
def build_data(build)
Ci::WebHookService.new.send :build_data, build
end
end
require 'spec_helper'
describe CreateCommitBuildsService, services: true do
let(:service) { CreateCommitBuildsService.new }
let(:project) { FactoryGirl.create(:empty_project) }
let(:user) { nil }
before do
stub_ci_commit_to_return_yaml_file
end
describe :execute do
context 'valid params' do
let(:commit) do
service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
commits: [{ message: "Message" }]
)
end
it { expect(commit).to be_kind_of(Ci::Commit) }
it { expect(commit).to be_valid }
it { expect(commit).to be_persisted }
it { expect(commit).to eq(project.ci_commits.last) }
it { expect(commit.builds.first).to be_kind_of(Ci::Build) }
end
context "skip tag if there is no build for it" do
it "creates commit if there is appropriate job" do
result = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: [{ message: "Message" }]
)
expect(result).to be_persisted
end
it "creates commit if there is no appropriate job but deploy job has right ref setting" do
config = YAML.dump({ deploy: { deploy: "ls", only: ["0_1"] } })
stub_ci_commit_yaml_file(config)
result = service.execute(project, user,
ref: 'refs/heads/0_1',
before: '00000000',
after: '31das312',
commits: [{ message: "Message" }]
)
expect(result).to be_persisted
end
end
it 'skips commits without .gitlab-ci.yml' do
stub_ci_commit_yaml_file(nil)
result = service.execute(project, user,
ref: 'refs/heads/0_1',
before: '00000000',
after: '31das312',
commits: [{ message: 'Message' }]
)
expect(result).to be_persisted
expect(result.builds.any?).to be_falsey
expect(result.status).to eq('skipped')
expect(result.yaml_errors).to be_nil
end
it 'skips commits if yaml is invalid' do
message = 'message'
allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
stub_ci_commit_yaml_file('invalid: file: file')
commits = [{ message: message }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq('failed')
expect(commit.yaml_errors).to_not be_nil
end
describe :ci_skip? do
let(:message) { "some message[ci skip]" }
before do
allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
end
it "skips builds creation if there is [ci skip] tag in commit message" do
commits = [{ message: message }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
end
it "does not skips builds creation if there is no [ci skip] tag in commit message" do
allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { "some message" }
commits = [{ message: "some message" }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.first.name).to eq("staging")
end
it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do
stub_ci_commit_yaml_file('invalid: file: fiile')
commits = [{ message: message }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
expect(commit.yaml_errors).to be_nil
end
end
it "skips build creation if there are already builds" do
allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { gitlab_ci_yaml }
commits = [{ message: "message" }]
commit = service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.count(:all)).to eq(2)
commit = service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.builds.count(:all)).to eq(2)
end
it "creates commit with failed status if yaml is invalid" do
stub_ci_commit_yaml_file('invalid: file')
commits = [{ message: "some message" }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
commits: commits
)
expect(commit.status).to eq("failed")
expect(commit.builds.any?).to be false
end
end
end
...@@ -11,7 +11,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do ...@@ -11,7 +11,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
end end
let(:project) { create(:project) } let(:project) { create(:project) }
let(:ci_commit) { create(:ci_commit_with_one_job, ref: mr_merge_if_green_enabled.source_branch, gl_project: project) } let(:ci_commit) { create(:ci_commit_with_one_job, ref: mr_merge_if_green_enabled.source_branch, project: project) }
let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, commit_message: 'Awesome message') } let(:service) { MergeRequests::MergeWhenBuildSucceedsService.new(project, user, commit_message: 'Awesome message') }
describe "#execute" do describe "#execute" do
......
...@@ -21,8 +21,8 @@ module StubGitlabCalls ...@@ -21,8 +21,8 @@ module StubGitlabCalls
allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { ci_yaml } allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { ci_yaml }
end end
def stub_ci_service_disabled def stub_ci_builds_disabled
allow_any_instance_of(GitlabCiService).to receive(:active).and_return(false) allow_any_instance_of(Project).to receive(:builds_enabled?).and_return(false)
end end
private private
......
require 'spec_helper'
describe BuildEmailWorker do
include RepoHelpers
let(:build) { create(:ci_build) }
let(:user) { create(:user) }
let(:data) { Gitlab::BuildDataBuilder.build(build) }
subject { BuildEmailWorker.new }
before do
allow(build).to receive(:execute_hooks).and_return(false)
build.success
end
describe "#perform" do
it "sends mail" do
subject.perform(build.id, [user.email], data.stringify_keys)
email = ActionMailer::Base.deliveries.last
expect(email.subject).to include('Build success for')
expect(email.to).to eq([user.email])
end
it "gracefully handles an input SMTP error" do
ActionMailer::Base.deliveries.clear
allow(Notify).to receive(:build_success_email).and_raise(Net::SMTPFatalError)
subject.perform(build.id, [user.email], data.stringify_keys)
expect(ActionMailer::Base.deliveries.count).to eq(0)
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