bamboo_service.rb 3.85 KB
Newer Older
1 2
# frozen_string_literal: true

Drew Blessing's avatar
Drew Blessing committed
3
class BambooService < CiService
4 5
  include ReactiveService

Drew Blessing's avatar
Drew Blessing committed
6 7
  prop_accessor :bamboo_url, :build_key, :username, :password

8
  validates :bamboo_url, presence: true, public_url: true, if: :activated?
Drew Blessing's avatar
Drew Blessing committed
9
  validates :build_key, presence: true, if: :activated?
10 11
  validates :username,
    presence: true,
12
    if: ->(service) { service.activated? && service.password }
13 14
  validates :password,
    presence: true,
15
    if: ->(service) { service.activated? && service.username }
Drew Blessing's avatar
Drew Blessing committed
16 17 18 19

  attr_accessor :response

  after_save :compose_service_hook, if: :activated?
20
  before_update :reset_password
Drew Blessing's avatar
Drew Blessing committed
21 22 23 24 25 26

  def compose_service_hook
    hook = service_hook || build_service_hook
    hook.save
  end

27
  def reset_password
28
    if bamboo_url_changed? && !password_touched?
29 30 31 32
      self.password = nil
    end
  end

Drew Blessing's avatar
Drew Blessing committed
33 34 35 36 37 38 39 40 41 42 43 44
  def title
    'Atlassian Bamboo CI'
  end

  def description
    'A continuous integration and build server'
  end

  def help
    'You must set up automatic revision labeling and a repository trigger in Bamboo.'
  end

45
  def self.to_param
Drew Blessing's avatar
Drew Blessing committed
46 47 48 49 50 51
    'bamboo'
  end

  def fields
    [
        { type: 'text', name: 'bamboo_url',
52
          placeholder: 'Bamboo root URL like https://bamboo.example.com', required: true },
Drew Blessing's avatar
Drew Blessing committed
53
        { type: 'text', name: 'build_key',
54
          placeholder: 'Bamboo build plan key like KEY', required: true },
Drew Blessing's avatar
Drew Blessing committed
55 56
        { type: 'text', name: 'username',
          placeholder: 'A user with API access, if applicable' },
57
        { type: 'password', name: 'password' }
Drew Blessing's avatar
Drew Blessing committed
58 59 60
    ]
  end

61 62
  def build_page(sha, ref)
    with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
Drew Blessing's avatar
Drew Blessing committed
63 64
  end

65 66 67
  def commit_status(sha, ref)
    with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
  end
Drew Blessing's avatar
Drew Blessing committed
68

69 70 71
  def execute(data)
    return unless supported_events.include?(data[:object_kind])

72
    get_path("updateAndBuild.action", { buildKey: build_key })
73 74 75
  end

  def calculate_reactive_cache(sha, ref)
76
    response = get_path("rest/api/latest/result/byChangeset/#{sha}")
77 78 79 80 81 82

    { build_page: read_build_page(response), commit_status: read_commit_status(response) }
  end

  private

83 84 85 86 87 88 89 90 91 92
  def get_build_result(response)
    return if response.code != 200

    # May be nil if no result, a single result hash, or an array if multiple results for a given changeset.
    result = response.dig('results', 'results', 'result')

    # In case of multiple results, arbitrarily assume the last one is the most relevant.
    return result.last if result.is_a?(Array)

    result
93 94
  end

95
  def read_build_page(response)
96
    result = get_build_result(response)
97
    key =
98
      if result.blank?
99 100 101 102
        # If actual build link can't be determined, send user to build summary page.
        build_key
      else
        # If actual build link is available, go to build result page.
103
        result.dig('planResultKey', 'key')
104 105 106
      end

    build_url("browse/#{key}")
Drew Blessing's avatar
Drew Blessing committed
107 108
  end

109 110
  def read_commit_status(response)
    return :error unless response.code == 200 || response.code == 404
Drew Blessing's avatar
Drew Blessing committed
111

112 113 114 115 116 117 118 119 120
    result = get_build_result(response)
    status =
      if result.blank?
        'Pending'
      else
        result.dig('buildState')
      end

    return :error unless status.present?
Drew Blessing's avatar
Drew Blessing committed
121 122 123 124 125 126 127 128 129 130 131 132

    if status.include?('Success')
      'success'
    elsif status.include?('Failed')
      'failed'
    elsif status.include?('Pending')
      'pending'
    else
      :error
    end
  end

133
  def build_url(path)
134
    Gitlab::Utils.append_path(bamboo_url, path)
135 136
  end

137
  def get_path(path, query_params = {})
138
    url = build_url(path)
139 140

    if username.blank? && password.blank?
141
      Gitlab::HTTP.get(url, verify: false, query: query_params)
142
    else
143 144 145 146 147 148 149 150
      query_params[:os_authType] = 'basic'
      Gitlab::HTTP.get(url,
                       verify: false,
                       query: query_params,
                       basic_auth: {
                         username: username,
                         password: password
                       })
151
    end
Drew Blessing's avatar
Drew Blessing committed
152 153
  end
end