project.rb 6.21 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
# == 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
31

32 33
    include Ci::ProjectStatus

34
    belongs_to :gl_project, class_name: '::Project', foreign_key: :gitlab_id
35

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
    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

51 52
    delegate :name_with_namespace, :path_with_namespace, :web_url, :http_url_to_repo, :ssh_url_to_repo, to: :gl_project

53 54 55
    #
    # Validations
    #
56
    validates_presence_of :timeout, :token, :default_ref, :gitlab_id
57 58 59 60

    validates_uniqueness_of :gitlab_id

    validates :polling_interval,
Kamil Trzcinski's avatar
WIP  
Kamil Trzcinski committed
61 62
              presence: true,
              if: ->(project) { project.always_build.present? }
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

    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 = {
78 79 80
          gitlab_id:                project.id,
          default_ref:              project.default_branch || 'master',
          email_add_pusher:         current_application_settings.add_pusher,
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
          email_only_broken_builds: current_application_settings.all_broken_builds,
        }

        project = Ci::Project.new(params)
        project.build_missing_services
        project
      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}").
96
        where("#{Ci::RunnerProject.table_name}.project_id" => nil)
97 98 99
      end

      def ordered_by_last_commit_date
Kamil Trzcinski's avatar
Kamil Trzcinski committed
100 101
        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").
102 103
          order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC")
      end
104
    end
105

106 107 108 109 110 111 112 113 114 115
    def name
      name_with_namespace
    end

    def path
      path_with_namespace
    end

    def gitlab_url
      web_url
116 117 118 119 120 121 122 123 124 125 126 127
    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?
Kamil Trzcinski's avatar
WIP  
Kamil Trzcinski committed
128
      self.default_ref ||= 'master'
129 130 131
    end

    def tracked_refs
Kamil Trzcinski's avatar
WIP  
Kamil Trzcinski committed
132
      @tracked_refs ||= default_ref.split(",").map { |ref| ref.strip }
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
    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}@"
172
      http_url_to_repo.sub(/^https?:\/\//) do |prefix|
173 174 175 176 177 178 179 180 181 182 183 184 185 186
        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`
187
        self.send :"create_#{service_name}_service" if service.nil?
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
      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
206 207

    def commits
208
      gl_project.ci_commits.ordered
209 210 211 212 213
    end

    def builds
      gl_project.ci_builds
    end
214 215
  end
end