remote_mirror.rb 3.65 KB
Newer Older
1 2 3
class RemoteMirror < ActiveRecord::Base
  include AfterCommitQueue

4 5 6 7
  attr_encrypted :credentials,
                 key: Gitlab::Application.secrets.db_key_base,
                 marshal: true,
                 encode: true,
8
                 mode: :per_attribute_iv_and_salt,
James Lopez's avatar
James Lopez committed
9
                 insecure_mode: true,
10
                 algorithm: 'aes-256-cbc'
11

12
  belongs_to :project, inverse_of: :remote_mirrors
13

14
  validates :url, presence: true, url: { protocols: %w(ssh git http https), allow_blank: true }
15
  validate  :url_availability, if: -> (mirror) { mirror.url_changed? || mirror.enabled? }
16

17 18
  after_save :refresh_remote, if: :mirror_url_changed?
  after_update :reset_fields, if: :mirror_url_changed?
19 20 21 22
  after_destroy :remove_remote

  scope :enabled, -> { where(enabled: true) }
  scope :started, -> { with_update_status(:started) }
23
  scope :stuck,   -> { started.where('last_update_at < ? OR (last_update_at IS NULL AND updated_at < ?)', 1.day.ago, 1.day.ago) }
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

  state_machine :update_status, initial: :none do
    event :update_start do
      transition [:none, :finished] => :started
    end

    event :update_finish do
      transition started: :finished
    end

    event :update_fail do
      transition started: :failed
    end

    event :update_retry do
      transition failed: :started
    end

    state :started
    state :finished
    state :failed

    after_transition any => :started, do: :schedule_update_job

    after_transition started: :finished do |remote_mirror, transaction|
49
      timestamp = Time.now
50 51 52 53 54 55
      remote_mirror.update_attributes!(
        last_update_at: timestamp, last_successful_update_at: timestamp, last_error: nil
      )
    end

    after_transition started: :failed do |remote_mirror, transaction|
56
      remote_mirror.update(last_update_at: Time.now)
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
    end
  end

  def ref_name
    "remote_mirror_#{id}"
  end

  def update_failed?
    update_status == 'failed'
  end

  def update_in_progress?
    update_status == 'started'
  end

  def sync
    return if !enabled || update_in_progress?

75
    update_failed? ? update_retry : update_start
76 77 78 79 80 81 82 83
  end

  def mark_for_delete_if_blank_url
    mark_for_destruction if url.blank?
  end

  def mark_as_failed(error_message)
    update_fail
84
    update_column(:last_error, Gitlab::UrlSanitizer.sanitize(error_message))
85 86 87
  end

  def url=(value)
88
    mirror_url = Gitlab::UrlSanitizer.new(value)
89
    self.credentials = mirror_url.credentials
90 91 92 93

    super(mirror_url.sanitized_url)
  end

94 95
  def url
    if super
96
      Gitlab::UrlSanitizer.new(super, credentials: credentials).full_url
97 98 99 100 101 102 103 104
    end
  end

  def safe_url
    return if url.nil?

    result = URI.parse(url)
    result.password = '*****' if result.password
Valery Sizov's avatar
Valery Sizov committed
105
    result.user = '*****' if result.user && result.user != "git" # tokens or other data may be saved as user
106 107 108
    result.to_s
  end

109 110 111
  private

  def url_availability
112
    if project.import_url == url && project.mirror?
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
      errors.add(:url, 'is already in use')
    end
  end

  def reset_fields
    update_columns(
      last_error: nil,
      last_update_at: nil,
      last_successful_update_at: nil,
      update_status: 'finished'
    )
  end

  def schedule_update_job
    run_after_commit(:add_update_job)
  end

  def add_update_job
    if project.repository_exists?
      RepositoryUpdateRemoteMirrorWorker.perform_async(self.id)
    end
  end

  def refresh_remote
    project.repository.remove_remote(ref_name)
138
    project.repository.add_remote(ref_name, url)
139 140 141
  end

  def remove_remote
142 143 144
    if project # could be pending to delete so don't need to touch the git repository
      project.repository.remove_remote(ref_name)
    end
145
  end
146 147 148 149

  def mirror_url_changed?
    url_changed? || encrypted_credentials_changed?
  end
150
end