Skip to content
Snippets Groups Projects
Commit cccd269d authored by Kamil Trzcińśki's avatar Kamil Trzcińśki
Browse files

Merge branch 'ci-and-ce-sitting-in-a-tree-k-i-s-s-i-n-g' into 'master'

Merge CI into CE

First step of #2164.

- [x] Merge latest CE master
- [x] Make application start
- [x] Re-use gitlab sessions (remove CI oauth part)
- [x] Get rid of gitlab_ci.yml config
- [x] Make tests start
- [x] Make most CI features works
- [x] Make tests green
- [x] Write migration documentation
- [x] Add CI builds to CE backup

See merge request !1204
parents 7d59ba00 ac8d2eb0
No related branches found
No related tags found
No related merge requests found
Showing
with 1651 additions and 3 deletions
# == Schema Information
#
# Table name: 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
def self.current
Ci::ApplicationSetting.last
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
# == Schema Information
#
# Table name: builds
#
# id :integer not null, primary key
# project_id :integer
# status :string(255)
# finished_at :datetime
# trace :text
# created_at :datetime
# updated_at :datetime
# started_at :datetime
# runner_id :integer
# commit_id :integer
# coverage :float
# commands :text
# job_id :integer
# name :string(255)
# options :text
# allow_failure :boolean default(FALSE), not null
# stage :string(255)
# deploy :boolean default(FALSE)
# trigger_request_id :integer
#
module Ci
class Build < ActiveRecord::Base
extend Ci::Model
LAZY_ATTRIBUTES = ['trace']
belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :project, class_name: 'Ci::Project'
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
serialize :options
validates :commit, presence: true
validates :status, presence: true
validates :coverage, numericality: true, allow_blank: true
scope :running, ->() { where(status: "running") }
scope :pending, ->() { where(status: "pending") }
scope :success, ->() { where(status: "success") }
scope :failed, ->() { where(status: "failed") }
scope :unstarted, ->() { where(runner_id: nil) }
scope :running_or_pending, ->() { where(status:[:running, :pending]) }
acts_as_taggable
# To prevent db load megabytes of data from trace
default_scope -> { select(Ci::Build.columns_without_lazy) }
class << self
def columns_without_lazy
(column_names - LAZY_ATTRIBUTES).map do |column_name|
"#{table_name}.#{column_name}"
end
end
def last_month
where('created_at > ?', Date.today - 1.month)
end
def first_pending
pending.unstarted.order('created_at ASC').first
end
def create_from(build)
new_build = build.dup
new_build.status = :pending
new_build.runner_id = nil
new_build.save
end
def retry(build)
new_build = Ci::Build.new(status: :pending)
new_build.options = build.options
new_build.commands = build.commands
new_build.tag_list = build.tag_list
new_build.commit_id = build.commit_id
new_build.project_id = build.project_id
new_build.name = build.name
new_build.allow_failure = build.allow_failure
new_build.stage = build.stage
new_build.trigger_request = build.trigger_request
new_build.save
new_build
end
end
state_machine :status, initial: :pending do
event :run do
transition pending: :running
end
event :drop do
transition running: :failed
end
event :success do
transition running: :success
end
event :cancel do
transition [:pending, :running] => :canceled
end
after_transition pending: :running do |build, transition|
build.update_attributes started_at: Time.now
end
after_transition any => [:success, :failed, :canceled] do |build, transition|
build.update_attributes finished_at: Time.now
project = build.project
if project.web_hooks?
Ci::WebHookService.new.build_end(build)
end
if build.commit.success?
build.commit.create_next_builds(build.trigger_request)
end
project.execute_services(build)
if project.coverage_enabled?
build.update_coverage
end
end
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
state :success, value: 'success'
state :canceled, value: 'canceled'
end
delegate :sha, :short_sha, :before_sha, :ref,
to: :commit, prefix: false
def trace_html
html = Ci::Ansi2html::convert(trace) if trace.present?
html ||= ''
end
def trace
if project && read_attribute(:trace).present?
read_attribute(:trace).gsub(project.token, 'xxxxxx')
end
end
def started?
!pending? && !canceled? && started_at
end
def active?
running? || pending?
end
def complete?
canceled? || success? || failed?
end
def ignored?
failed? && allow_failure?
end
def timeout
project.timeout
end
def variables
yaml_variables + project_variables + trigger_variables
end
def duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
end
def project
commit.project
end
def project_id
commit.project_id
end
def project_name
project.name
end
def repo_url
project.repo_url_with_auth
end
def allow_git_fetch
project.allow_git_fetch
end
def update_coverage
coverage = extract_coverage(trace, project.coverage_regex)
if coverage.is_a? Numeric
update_attributes(coverage: coverage)
end
end
def extract_coverage(text, regex)
begin
matches = text.gsub(Regexp.new(regex)).to_a.last
coverage = matches.gsub(/\d+(\.\d+)?/).first
if coverage.present?
coverage.to_f
end
rescue => ex
# if bad regex or something goes wrong we dont want to interrupt transition
# so we just silentrly ignore error for now
end
end
def trace
if File.exist?(path_to_trace)
File.read(path_to_trace)
else
# backward compatibility
read_attribute :trace
end
end
def trace=(trace)
unless Dir.exists? dir_to_trace
FileUtils.mkdir_p dir_to_trace
end
File.write(path_to_trace, trace)
end
def dir_to_trace
File.join(
Settings.gitlab_ci.builds_path,
created_at.utc.strftime("%Y_%m"),
project.id.to_s
)
end
def path_to_trace
"#{dir_to_trace}/#{id}.log"
end
private
def yaml_variables
if commit.config_processor
commit.config_processor.variables.map do |key, value|
{ key: key, value: value, public: true }
end
else
[]
end
end
def project_variables
project.variables.map do |variable|
{ key: variable.key, value: variable.value, public: false }
end
end
def trigger_variables
if trigger_request && trigger_request.variables
trigger_request.variables.map do |key, value|
{ key: key, value: value, public: false }
end
else
[]
end
end
end
end
# == Schema Information
#
# Table name: commits
#
# id :integer not null, primary key
# project_id :integer
# ref :string(255)
# sha :string(255)
# before_sha :string(255)
# push_data :text
# created_at :datetime
# updated_at :datetime
# tag :boolean default(FALSE)
# yaml_errors :text
# committed_at :datetime
#
module Ci
class Commit < ActiveRecord::Base
extend Ci::Model
belongs_to :project, class_name: 'Ci::Project'
has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
serialize :push_data
validates_presence_of :ref, :sha, :before_sha, :push_data
validate :valid_commit_sha
def self.truncate_sha(sha)
sha[0...8]
end
def to_param
sha
end
def last_build
builds.order(:id).last
end
def retry
builds_without_retry.each do |build|
Ci::Build.retry(build)
end
end
def valid_commit_sha
if self.sha == Ci::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)")
end
end
def new_branch?
before_sha == Ci::Git::BLANK_SHA
end
def compare?
!new_branch?
end
def git_author_name
commit_data[:author][:name] if commit_data && commit_data[:author]
end
def git_author_email
commit_data[:author][:email] if commit_data && commit_data[:author]
end
def git_commit_message
commit_data[:message] if commit_data && commit_data[:message]
end
def short_before_sha
Ci::Commit.truncate_sha(before_sha)
end
def short_sha
Ci::Commit.truncate_sha(sha)
end
def commit_data
push_data[:commits].find do |commit|
commit[:id] == sha
end
rescue
nil
end
def project_recipients
recipients = project.email_recipients.split(' ')
if project.email_add_pusher? && push_data[:user_email].present?
recipients << push_data[:user_email]
end
recipients.uniq
end
def stage
return unless config_processor
stages = builds_without_retry.select(&:active?).map(&:stage)
config_processor.stages.find { |stage| stages.include? stage }
end
def create_builds_for_stage(stage, trigger_request)
return if skip_ci? && trigger_request.blank?
return unless config_processor
builds_attrs = config_processor.builds_for_stage_and_ref(stage, ref, tag)
builds_attrs.map do |build_attrs|
builds.create!({
project: project,
name: build_attrs[:name],
commands: build_attrs[:script],
tag_list: build_attrs[:tags],
options: build_attrs[:options],
allow_failure: build_attrs[:allow_failure],
stage: build_attrs[:stage],
trigger_request: trigger_request,
})
end
end
def create_next_builds(trigger_request)
return if skip_ci? && trigger_request.blank?
return unless config_processor
stages = builds.where(trigger_request: trigger_request).group_by(&:stage)
config_processor.stages.any? do |stage|
!stages.include?(stage) && create_builds_for_stage(stage, trigger_request).present?
end
end
def create_builds(trigger_request = nil)
return if skip_ci? && trigger_request.blank?
return unless config_processor
config_processor.stages.any? do |stage|
create_builds_for_stage(stage, trigger_request).present?
end
end
def builds_without_retry
@builds_without_retry ||=
begin
grouped_builds = builds.group_by(&:name)
grouped_builds.map do |name, builds|
builds.sort_by(&:id).last
end
end
end
def builds_without_retry_sorted
return builds_without_retry unless config_processor
stages = config_processor.stages
builds_without_retry.sort_by do |build|
[stages.index(build.stage) || -1, build.name || ""]
end
end
def retried_builds
@retried_builds ||= (builds.order(id: :desc) - builds_without_retry)
end
def status
if skip_ci?
return 'skipped'
elsif yaml_errors.present?
return 'failed'
elsif builds.none?
return 'skipped'
elsif success?
'success'
elsif pending?
'pending'
elsif running?
'running'
elsif canceled?
'canceled'
else
'failed'
end
end
def pending?
builds_without_retry.all? do |build|
build.pending?
end
end
def running?
builds_without_retry.any? do |build|
build.running? || build.pending?
end
end
def success?
builds_without_retry.all? do |build|
build.success? || build.ignored?
end
end
def failed?
status == 'failed'
end
def canceled?
builds_without_retry.all? do |build|
build.canceled?
end
end
def duration
@duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i
end
def finished_at
@finished_at ||= builds.order('finished_at DESC').first.try(:finished_at)
end
def coverage
if project.coverage_enabled?
coverage_array = builds_without_retry.map(&:coverage).compact
if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
end
end
def matrix?
builds_without_retry.size > 1
end
def config_processor
@config_processor ||= Ci::GitlabCiYamlProcessor.new(push_data[:ci_yaml_file] || project.generated_yaml_config)
rescue Ci::GitlabCiYamlProcessor::ValidationError => e
save_yaml_error(e.message)
nil
rescue Exception => e
logger.error e.message + "\n" + e.backtrace.join("\n")
save_yaml_error("Undefined yaml error")
nil
end
def skip_ci?
return false if builds.any?
commits = push_data[:commits]
commits.present? && commits.last[:message] =~ /(\[ci skip\])/
end
def update_committed!
update!(committed_at: DateTime.now)
end
private
def save_yaml_error(error)
return if self.yaml_errors?
self.yaml_errors = error
save
end
end
end
# == 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
#
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: 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
#
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 :commits, ->() { order(:committed_at) }, dependent: :destroy, class_name: 'Ci::Commit'
has_many :builds, through: :commits, dependent: :destroy, class_name: 'Ci::Build'
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
#
# Validations
#
validates_presence_of :name, :timeout, :token, :default_ref,
:path, :ssh_url_to_repo, :gitlab_id
validates_uniqueness_of :gitlab_id
validates :polling_interval,
presence: true,
if: ->(project) { project.always_build.present? }
scope :public_only, ->() { where(public: true) }
before_validation :set_default_values
class << self
include Ci::CurrentSettings
def base_build_script
<<-eos
git submodule update --init
ls -la
eos
end
def parse(project)
params = {
name: project.name_with_namespace,
gitlab_id: project.id,
path: project.path_with_namespace,
default_ref: project.default_branch || 'master',
ssh_url_to_repo: project.ssh_url_to_repo,
email_add_pusher: current_application_settings.add_pusher,
email_only_broken_builds: current_application_settings.all_broken_builds,
}
project = Ci::Project.new(params)
project.build_missing_services
project
end
# TODO: remove
def from_gitlab(user, scope = :owned, options)
opts = user.authenticate_options
opts.merge! options
raise 'Implement me of fix'
#projects = Ci::Network.new.projects(opts.compact, scope)
if projects
projects.map { |pr| OpenStruct.new(pr) }
else
[]
end
end
def already_added?(project)
where(gitlab_id: project.id).any?
end
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 project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY project_id)"
joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.id = last_commit.project_id").
order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC")
end
def search(query)
where("LOWER(#{Ci::Project.table_name}.name) LIKE :query",
query: "%#{query.try(:downcase)}%")
end
end
def any_runners?
if runners.active.any?
return true
end
shared_runners_enabled && Ci::Runner.shared.active.any?
end
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
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}@"
url = gitlab_url + ".git"
url.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`
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 gitlab_url
File.join(Gitlab.config.gitlab.url, path)
end
def setup_finished?
commits.any?
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
# only check for toggling build status within same ref.
def last_commit_changed_status?
ref = last_commit.ref
last_commits = commits.where(ref: ref).last(2)
if last_commits.size < 2
false
else
last_commits[0].status != last_commits[1].status
end
end
def last_commit_for_ref(ref)
commits.where(ref: ref).last
end
end
end
# == Schema Information
#
# Table name: runners
#
# id :integer not null, primary key
# token :string(255)
# created_at :datetime
# updated_at :datetime
# description :string(255)
# contacted_at :datetime
# active :boolean default(TRUE), not null
# is_shared :boolean default(FALSE)
# name :string(255)
# version :string(255)
# revision :string(255)
# platform :string(255)
# architecture :string(255)
#
module Ci
class Runner < ActiveRecord::Base
extend Ci::Model
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
has_many :projects, through: :runner_projects, class_name: 'Ci::Project'
has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
before_validation :set_default_values
scope :specific, ->() { where(is_shared: false) }
scope :shared, ->() { where(is_shared: true) }
scope :active, ->() { where(active: true) }
scope :paused, ->() { where(active: false) }
acts_as_taggable
def self.search(query)
where('LOWER(ci_runners.token) LIKE :query OR LOWER(ci_runners.description) like :query',
query: "%#{query.try(:downcase)}%")
end
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
end
def assign_to(project, current_user = nil)
self.is_shared = false if shared?
self.save
project.runner_projects.create!(runner_id: self.id)
end
def display_name
return token unless !description.blank?
description
end
def shared?
is_shared
end
def belongs_to_one_project?
runner_projects.count == 1
end
def specific?
!shared?
end
def only_for?(project)
projects == [project]
end
def short_sha
token[0...10]
end
end
end
# == Schema Information
#
# Table name: runner_projects
#
# id :integer not null, primary key
# runner_id :integer not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
#
module Ci
class RunnerProject < ActiveRecord::Base
extend Ci::Model
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :project, class_name: 'Ci::Project'
validates_uniqueness_of :runner_id, scope: :project_id
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
#
# 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
# == Schema Information
#
# Table name: triggers
#
# id :integer not null, primary key
# token :string(255)
# project_id :integer not null
# deleted_at :datetime
# created_at :datetime
# updated_at :datetime
#
module Ci
class Trigger < ActiveRecord::Base
extend Ci::Model
acts_as_paranoid
belongs_to :project, class_name: 'Ci::Project'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
validates_presence_of :token
validates_uniqueness_of :token
before_validation :set_default_values
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
end
def last_trigger_request
trigger_requests.last
end
def short_token
token[0...10]
end
end
end
# == Schema Information
#
# Table name: trigger_requests
#
# id :integer not null, primary key
# trigger_id :integer not null
# variables :text
# created_at :datetime
# updated_at :datetime
# commit_id :integer
#
module Ci
class TriggerRequest < ActiveRecord::Base
extend Ci::Model
belongs_to :trigger, class_name: 'Ci::Trigger'
belongs_to :commit, class_name: 'Ci::Commit'
has_many :builds, class_name: 'Ci::Build'
serialize :variables
end
end
# == Schema Information
#
# Table name: variables
#
# id :integer not null, primary key
# project_id :integer not null
# key :string(255)
# value :text
# encrypted_value :text
# encrypted_value_salt :string(255)
# encrypted_value_iv :string(255)
#
module Ci
class Variable < ActiveRecord::Base
extend Ci::Model
belongs_to :project, class_name: 'Ci::Project'
validates_presence_of :key
validates_uniqueness_of :key, scope: :project_id
attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base
end
end
# == Schema Information
#
# Table name: 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,
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
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
Loading
Loading
@@ -329,7 +329,7 @@ class Project < ActiveRecord::Base
end
 
def web_url
Rails.application.routes.url_helpers.namespace_project_url(self.namespace, self)
Gitlab::Application.routes.url_helpers.namespace_project_url(self.namespace, self)
end
 
def web_url_without_protocol
Loading
Loading
@@ -455,7 +455,7 @@ class Project < ActiveRecord::Base
if avatar.present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
Rails.application.routes.url_helpers.namespace_project_avatar_url(namespace, self)
Gitlab::Application.routes.url_helpers.namespace_project_avatar_url(namespace, self)
end
end
 
Loading
Loading
module Ci
class HipChatMessage
attr_reader :build
def initialize(build)
@build = build
end
def to_s
lines = Array.new
lines.push("<a href=\"#{Ci::RoutesHelper.ci_project_url(project)}\">#{project.name}</a> - ")
if commit.matrix?
lines.push("<a href=\"#{Ci::RoutesHelper.ci_project_ref_commits_url(project, commit.ref, commit.sha)}\">Commit ##{commit.id}</a></br>")
else
first_build = commit.builds_without_retry.first
lines.push("<a href=\"#{Ci::RoutesHelper.ci_project_build_url(project, first_build)}\">Build '#{first_build.name}' ##{first_build.id}</a></br>")
end
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: 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.builds_without_retry.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: 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.builds_without_retry.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.commit.project_recipients.each do |recipient|
case build.status.to_sym
when :success
mailer.build_success_email(build.id, recipient)
when :failed
mailer.build_fail_email(build.id, recipient)
end
end
end
private
def update_project
project.save!
end
def mailer
Ci::Notify.delay
end
end
end
require 'slack-notifier'
module Ci
class SlackMessage
def initialize(commit)
@commit = commit
end
def pretext
''
end
def color
attachment_color
end
def fallback
format(attachment_message)
end
def attachments
fields = []
if commit.matrix?
commit.builds_without_retry.each do |build|
next if build.allow_failure?
next unless build.failed?
fields << {
title: build.name,
value: "Build <#{Ci::RoutesHelper.ci_project_build_url(project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)."
}
end
end
[{
text: attachment_message,
color: attachment_color,
fields: fields
}]
end
private
attr_reader :commit
def attachment_message
out = "<#{Ci::RoutesHelper.ci_project_url(project)}|#{project_name}>: "
if commit.matrix?
out << "Commit <#{Ci::RoutesHelper.ci_project_ref_commits_url(project, commit.ref, commit.sha)}|\##{commit.id}> "
else
build = commit.builds_without_retry.first
out << "Build <#{Ci::RoutesHelper.ci_project_build_path(project, build)}|\##{build.id}> "
end
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: 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.builds_without_retry.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
Loading
Loading
@@ -19,7 +19,7 @@
#
 
class GitlabIssueTrackerService < IssueTrackerService
include Rails.application.routes.url_helpers
include Gitlab::Application.routes.url_helpers
 
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
 
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment