Skip to content

Commit f2a809a

Browse files
committed
Clean Up DownloadFilesFromTeamCity build task
TeamCity has changed or removed its API for retrieving available tags, and we had already been falling through to .lastSuccessful in both instances that were still querying this API. Change-Id: I0892cf4f04b054607b2e145476e0cccbacee515f (partial cherry pick from commit e8a0173) Change-Id: I7794c1d941080d4df784fa707b1c3c94b8ea1327
1 parent b9ec436 commit f2a809a

1 file changed

Lines changed: 7 additions & 201 deletions

File tree

Lines changed: 7 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,31 @@
1-
// Copyright (c) 2016-2017 SIL International
1+
// Copyright (c) 2016-2022 SIL International
22
// This software is licensed under the LGPL, version 2.1 or later
33
// (http://www.gnu.org/licenses/lgpl-2.1.html)
44

5-
using System.Collections.Generic;
65
using System.IO;
76
using System.Linq;
8-
using System.Net;
9-
using System.Threading;
10-
using System.Xml.Linq;
117
using Microsoft.Build.Framework;
128

9+
// ReSharper disable once CheckNamespace
1310
namespace FwBuildTasks
1411
{
1512
/// <summary>
16-
/// Downloads artifacts from TeamCity for the given BuildType. Select in the following order:
17-
/// - If Tag or Query is specified, use them
18-
/// - If VersionInfo is specified, look for a matching tag (fw-9.0.0 before fw-9.0)
19-
/// - Otherwise, download .lastSuccessful
13+
/// Downloads artifacts from TeamCity for the given BuildType.
14+
/// If Tag or Query is specified, use them; otherwise, download .lastSuccessful
2015
///
2116
/// Usage:
2217
/// <DownloadFilesFromTeamCity
2318
/// Address="http://build.palaso.org/"
2419
/// BuildType="bt2"
2520
/// Tag=".lastSuccessful"
2621
/// Query="?branch=%3Cdefault%3E"
27-
/// VersionInfo="$(fwrt)/Src/MasterVersionInfo.txt"
2822
/// DownloadsDir="$(fwrt)/Downloads"
2923
/// Artifacts="@(ChorusFiles)"/>
3024
/// </summary>
3125
public class DownloadFilesFromTeamCity : DownloadFile
3226
{
3327
private const string ArtifactsUrlPart = "guestAuth/repository/download/";
34-
private const string BuildTypeUrlPart = "guestAuth/app/rest/10.0/buildTypes/id:{0}/";
35-
private const string BuildTagsUrlPart = "builds?locator=pinned:true&fields=build(tags(tag))";
3628
private const string DefaultTag = ".lastSuccessful";
37-
private const string TagSuffix = ".tcbuildtag";
38-
private const string QueryFormat = "?branch={0}";
3929

4030
/// <summary>
4131
/// TeamCity BuildType that contains the Artifacts.
@@ -50,209 +40,25 @@ public class DownloadFilesFromTeamCity : DownloadFile
5040
/// <summary>URL Query (e.g. ?branch=%3Cdefault%3E). Used only if FlexBridgeBuildType has no matching dependency.</summary>
5141
public string Query { get; set; }
5242

53-
/// <summary>Path to FLEx's MasterVersionInfo.txt. Used to guess a build tag if unavailable through the FLExBridge BT configuration.</summary>
54-
public string VersionInfo { get; set; }
55-
5643
/// <summary>(Semicolon-delimited) list of artifacts to download</summary>
5744
[Required]
5845
public string[] Artifacts { get; set; }
5946

6047
public override bool Execute()
6148
{
62-
// If the user specified a tag or query, it overrides whatever we might find by querying TeamCity
63-
if (!string.IsNullOrEmpty(Tag) || !string.IsNullOrEmpty(Query))
64-
{
65-
if (string.IsNullOrEmpty(Tag))
66-
Tag = DefaultTag;
67-
if (!string.IsNullOrEmpty(BuildType))
68-
return DownloadAllFiles();
69-
Log.LogError("Cannot use a Tag or Query without a BuildType");
70-
return false;
71-
}
72-
73-
switch (QueryTeamCity())
74-
{
75-
case TeamCityQueryResult.Found:
76-
case TeamCityQueryResult.FellThrough:
77-
return DownloadAllFiles();
78-
case TeamCityQueryResult.Failed:
79-
return false;
80-
default:
81-
Log.LogError("Unknown TeamCity Query Result. This is not necessarily bad, but this FwBuildTask doesn't know that.");
82-
return false;
83-
}
84-
}
49+
if (string.IsNullOrEmpty(Tag))
50+
Tag = DefaultTag;
8551

86-
protected bool DownloadAllFiles()
87-
{
8852
var addressBase = CombineUrl(Address, ArtifactsUrlPart, BuildType, Tag);
89-
Log.LogMessage("Downloading artifacts from {0}{1}", addressBase, Query == null ? null : string.Format(" with Query {0}", Query));
53+
Log.LogMessage("Downloading artifacts from {0}{1}", addressBase, Query == null ? null : $" with Query {Query}");
9054
// Return success iff all files download successfully
9155
return Artifacts.Aggregate(true, (successSoFar, file) => successSoFar
9256
&& ProcessDownloadFile(CombineUrl(addressBase, file) + Query, Path.Combine(DownloadsDir, file)));
9357
}
9458

95-
protected TeamCityQueryResult QueryTeamCity()
96-
{
97-
// Didn't find a matching dependency in FLExBridge; check for the most-specific version-tagged build, if any (e.g. fw-8.2.8~beta2~nightly)
98-
var availableTags = GetTagsFromBuildType();
99-
if (availableTags == null)
100-
{
101-
Log.LogError("Unable to retrieve dependencies for BuildType {0}. Check your connection and whether the BuildType exists", BuildType);
102-
return TeamCityQueryResult.Failed;
103-
}
104-
if (availableTags.Any())
105-
{
106-
Dictionary<string, string> versionParts;
107-
if (!string.IsNullOrEmpty(VersionInfo) && BuildUtils.ParseSymbolFile(VersionInfo, Log, out versionParts))
108-
{
109-
var tempTag = string.Format("fw-{0}.{1}.{2}~{3}",
110-
versionParts["FWMAJOR"], versionParts["FWMINOR"], versionParts["FWREVISION"], versionParts["FWBETAVERSION"]);
111-
tempTag = tempTag.Replace(" ", "").ToLowerInvariant(); // TC tags are spaceless and lowercase
112-
var versionDelims = new[] {'.', '~'};
113-
var idxDelim = tempTag.LastIndexOfAny(versionDelims);
114-
while (idxDelim > 0 && !availableTags.Contains(tempTag))
115-
{
116-
tempTag = tempTag.Remove(idxDelim);
117-
idxDelim = tempTag.LastIndexOfAny(versionDelims);
118-
}
119-
if (availableTags.Contains(tempTag))
120-
{
121-
Tag = tempTag + TagSuffix;
122-
Log.LogMessage("Found matching tag for BuildType {0}: {1}", BuildType, Tag);
123-
if (!string.IsNullOrEmpty(Query))
124-
{
125-
Log.LogWarning("Guessing Tags doesn't check queries. Guessed tag '{0}' for BuildType {1}, but it may not match {2}",
126-
Tag, BuildType, Query);
127-
}
128-
return TeamCityQueryResult.Found;
129-
}
130-
}
131-
}
132-
133-
// REVIEW (Hasso) 2016.10: using .lastSuccessful should be a WARNING on package builds (may lead to bit rot)
134-
// If all else fails, use the default "tag" .lastSuccessful
135-
Tag = DefaultTag;
136-
return TeamCityQueryResult.FellThrough;
137-
}
138-
139-
/// <returns>an array of tags on BuildType's pinned builds; null on any error</returns>
140-
protected string[] GetTagsFromBuildType()
141-
{
142-
string bXml;
143-
if (!MakeWebRequest(string.Format(CombineUrl(Address, BuildTypeUrlPart, BuildTagsUrlPart), BuildType), out bXml))
144-
return null;
145-
var buildsElt = XDocument.Load(new StringReader(bXml)).Element("builds");
146-
return buildsElt == null ? null : buildsElt.Elements("build").SelectMany(GetTagsFromBuildElt).ToArray();
147-
}
148-
149-
protected IEnumerable<string> GetTagsFromBuildElt(XElement buildElt)
150-
{
151-
var tagsElt = buildElt.Element("tags");
152-
if (tagsElt == null)
153-
return new string[0];
154-
return from tagElt in tagsElt.Elements("tag") select tagElt.Attribute("name") into nameAtt where nameAtt != null select nameAtt.Value;
155-
}
156-
157-
public bool MakeWebRequest(string url, out string response)
158-
{
159-
response = null;
160-
for (var retries = Retries; retries >= 0; --retries)
161-
{
162-
// Assign values to these objects here so that they can be referenced in the finally block
163-
HttpWebResponse webResponse = null;
164-
Stream remoteStream = null;
165-
Stream errorResponseStream = null;
166-
try
167-
{
168-
// Create a request for the specified remote file name
169-
var request = WebRequest.Create(url);
170-
// If a username or password have been given, use them
171-
if (!string.IsNullOrEmpty(Username) || !string.IsNullOrEmpty(Password))
172-
request.Credentials = new NetworkCredential(Username, Password);
173-
174-
// Prevent caching of requests so that we always download latest
175-
request.Headers[HttpRequestHeader.CacheControl] = "no-cache";
176-
177-
// Send the request to the server and retrieve the WebResponse object
178-
webResponse = (HttpWebResponse) request.GetResponse();
179-
remoteStream = webResponse.GetResponseStream();
180-
if (webResponse.StatusCode != HttpStatusCode.OK || remoteStream == null)
181-
{
182-
if (webResponse.StatusCode == HttpStatusCode.OK)
183-
Log.LogWarning("No data in response to request {0}", url);
184-
else
185-
Log.LogWarning("Unexpected Server Response[{0}] to request {1}", webResponse.StatusCode, url);
186-
if (retries > 0)
187-
{
188-
Log.LogMessage(MessageImportance.High, "Could not retrieve {0}. Trying {1} more times in {2}-minute intervals.",
189-
url, retries, RetryWaitTime / MillisPerMinute);
190-
Thread.Sleep(RetryWaitTime); // wait a minute
191-
}
192-
continue;
193-
}
194-
// Once the WebResponse object has been retrieved, get the stream object associated with the response's data
195-
using (var localStream = new StreamReader(remoteStream))
196-
response = localStream.ReadToEnd();
197-
return true;
198-
}
199-
catch (WebException e)
200-
{
201-
if (e.Response != null && (errorResponseStream = e.Response.GetResponseStream()) != null)
202-
{
203-
string html;
204-
using (var sr = new StreamReader(errorResponseStream))
205-
html = sr.ReadToEnd();
206-
Log.LogWarning("Unexpected response from {0}. Server responds {1}", url, html);
207-
return false; // The server is available, but it is likely the requested resource does not exist; don't keep trying
208-
}
209-
else
210-
{
211-
// Possibly a DNS error or some network outage between us and the server.
212-
Log.LogWarning("No response from {0}. Exception {1}. Status {2}.", url, e.Message, e.Status);
213-
}
214-
if (retries > 0)
215-
{
216-
Log.LogMessage(MessageImportance.High, "Could not retrieve {0}. Trying {1} more times in {2}-minute intervals.",
217-
url, retries, RetryWaitTime / MillisPerMinute);
218-
Thread.Sleep(RetryWaitTime); // wait a minute
219-
}
220-
}
221-
finally
222-
{
223-
// Close the response and streams objects here to make sure they're closed even if an exception is thrown at some point
224-
if (webResponse != null) webResponse.Close();
225-
if (remoteStream != null) remoteStream.Close();
226-
if (errorResponseStream != null) errorResponseStream.Close();
227-
}
228-
}
229-
return false;
230-
}
231-
23259
public static string CombineUrl(params string[] args)
23360
{
23461
return Path.Combine(args).Replace('\\', '/');
23562
}
236-
237-
public enum TeamCityQueryResult
238-
{
239-
Found,
240-
Failed,
241-
FellThrough
242-
}
243-
244-
public struct TcDependency
245-
{
246-
public string BuildTypeId;
247-
public string BuildTypeName;
248-
public string RevisionValue;
249-
public string RevisionBranch;
250-
251-
public override string ToString()
252-
{
253-
return string.Format("{0}/{1}{2}", BuildTypeId, RevisionValue,
254-
string.IsNullOrEmpty(RevisionBranch) ? null : string.Format(QueryFormat, RevisionBranch));
255-
}
256-
}
25763
}
25864
}

0 commit comments

Comments
 (0)