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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
# frozen_string_literal: true
module Integrations
class Teamcity < BaseCi
include PushDataValidations
include ReactivelyCached
prepend EnableSslVerification
TEAMCITY_SAAS_HOSTNAME = /\A[^\.]+\.teamcity\.com\z/i.freeze
prop_accessor :teamcity_url, :build_type, :username, :password
validates :teamcity_url, presence: true, public_url: true, if: :activated?
validates :build_type, presence: true, if: :activated?
validates :username,
presence: true,
if: ->(service) { service.activated? && service.password }
validates :password,
presence: true,
if: ->(service) { service.activated? && service.username }
attr_accessor :response
before_validation :reset_password
class << self
def to_param
'teamcity'
end
def supported_events
%w(push merge_request)
end
end
def reset_password
if teamcity_url_changed? && !password_touched?
self.password = nil
end
end
def title
'JetBrains TeamCity'
end
def description
s_('ProjectService|Run CI/CD pipelines with JetBrains TeamCity.')
end
def help
s_('To run CI/CD pipelines with JetBrains TeamCity, input the GitLab project details in the TeamCity project Version Control Settings.')
end
def fields
[
{
type: 'text',
name: 'teamcity_url',
title: s_('ProjectService|TeamCity server URL'),
placeholder: 'https://teamcity.example.com',
required: true
},
{
type: 'text',
name: 'build_type',
help: s_('ProjectService|The build configuration ID of the TeamCity project.'),
required: true
},
{
type: 'text',
name: 'username',
help: s_('ProjectService|Must have permission to trigger a manual build in TeamCity.')
},
{
type: 'password',
name: 'password',
non_empty_password_title: s_('ProjectService|Enter new password'),
non_empty_password_help: s_('ProjectService|Leave blank to use your current password')
}
]
end
def build_page(sha, ref)
with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
end
def commit_status(sha, ref)
with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
end
def calculate_reactive_cache(sha, ref)
response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,revision:#{sha}")
if response
{ build_page: read_build_page(response), commit_status: read_commit_status(response) }
else
{ build_page: teamcity_url, commit_status: :error }
end
end
def execute(data)
case data[:object_kind]
when 'push'
execute_push(data)
when 'merge_request'
execute_merge_request(data)
end
end
def enable_ssl_verification
original_value = Gitlab::Utils.to_boolean(properties['enable_ssl_verification'])
original_value.nil? ? (new_record? || url_is_saas?) : original_value
end
private
def url_is_saas?
parsed_url = Addressable::URI.parse(teamcity_url)
parsed_url&.scheme == 'https' && parsed_url.hostname.match?(TEAMCITY_SAAS_HOSTNAME)
rescue Addressable::URI::InvalidURIError
false
end
def execute_push(data)
branch = Gitlab::Git.ref_name(data[:ref])
post_to_build_queue(data, branch) if push_valid?(data)
end
def execute_merge_request(data)
branch = data[:object_attributes][:source_branch]
post_to_build_queue(data, branch) if merge_request_valid?(data)
end
def read_build_page(response)
if response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
build_url("viewLog.html?buildTypeId=#{build_type}")
else
# If actual build link is available, go to build result page.
built_id = response['build']['id']
build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}")
end
end
def read_commit_status(response)
return :error unless response.code == 200 || response.code == 404
status = if response.code == 404
'Pending'
else
response['build']['status']
end
return :error unless status.present?
if status.include?('SUCCESS')
'success'
elsif status.include?('FAILURE')
'failed'
elsif status.include?('Pending')
'pending'
else
:error
end
end
def build_url(path)
Gitlab::Utils.append_path(teamcity_url, path)
end
def get_path(path)
Gitlab::HTTP.try_get(build_url(path), verify: enable_ssl_verification, basic_auth: basic_auth, extra_log_info: { project_id: project_id }, use_read_total_timeout: true)
end
def post_to_build_queue(data, branch)
Gitlab::HTTP.post(
build_url('httpAuth/app/rest/buildQueue'),
body: "<build branchName=#{branch.encode(xml: :attr)}>"\
"<buildType id=#{build_type.encode(xml: :attr)}/>"\
'</build>',
headers: { 'Content-type' => 'application/xml' },
verify: enable_ssl_verification,
basic_auth: basic_auth,
use_read_total_timeout: true
)
end
def basic_auth
{ username: username, password: password }
end
end
end
|