-
Notifications
You must be signed in to change notification settings - Fork 9
/
ProductInfo.cs
304 lines (254 loc) · 11.5 KB
/
ProductInfo.cs
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Text.Json.Serialization;
using Serilog;
namespace CreateMatrix;
public class ProductInfo
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ProductType
{
None,
NxMeta,
NxWitness,
DWSpectrum,
WisenetWAVE
}
public ProductType Product { get; set; }
public List<VersionInfo> Versions { get; set; } = [];
public string GetCompany() => GetCompany(Product);
public string GetRelease() => GetRelease(Product);
public string GetDescription() => GetDescription(Product);
public string GetDocker(bool lsio) => GetDocker(Product, lsio);
// Used for release JSON API https://updates.vmsproxy.com/{release}/releases.json path
public static string GetRelease(ProductType productType)
{
return productType switch
{
ProductType.NxMeta => "metavms",
ProductType.NxWitness => "default",
ProductType.DWSpectrum => "digitalwatchdog",
ProductType.WisenetWAVE => "hanwha",
_ => throw new InvalidEnumArgumentException(nameof(Product))
};
}
// Used for ${COMPANY_NAME} mediaserver install path and user account
public static string GetCompany(ProductType productType)
{
return productType switch
{
ProductType.NxMeta => "networkoptix-metavms",
ProductType.NxWitness => "networkoptix",
ProductType.DWSpectrum => "digitalwatchdog",
ProductType.WisenetWAVE => "hanwha",
_ => throw new InvalidEnumArgumentException(nameof(Product))
};
}
// Used for ${LABEL_DESCRIPTION} in Dockerfile
public static string GetDescription(ProductType productType)
{
return productType switch
{
ProductType.NxMeta => "Nx Meta VMS",
ProductType.NxWitness => "Nx Witness VMS",
ProductType.DWSpectrum => "DW Spectrum IPVMS",
ProductType.WisenetWAVE => "Wisenet WAVE VMS",
_ => throw new InvalidEnumArgumentException(nameof(Product))
};
}
// Dockerfile name, excluding the .Dockerfile extension
// TODO: Consolidate with ImageInfo.SetName(), e.g. add enum for Ubuntu, LSIO, etc.
public static string GetDocker(ProductType productType, bool lsio)
{
return $"{productType}{(lsio ? "-LSIO" : "")}";
}
public static IEnumerable<ProductType> GetProductTypes()
{
// Create list of product types
return Enum.GetValues(typeof(ProductType)).Cast<ProductType>().Where(productType => productType != ProductType.None).ToList();
}
public static List<ProductInfo> GetProducts()
{
// Create list of all known products
return (from ProductType productType in GetProductTypes() select new ProductInfo { Product = productType }).ToList();
}
public void GetVersions()
{
// Match the logic with ReleasesTests.CreateProductInfo()
// TODO: Refactor to reduce duplication and chance of divergence
// Get version information using releases.json and package.json
Log.Logger.Information("{Product}: Getting online release information...", Product);
try
{
// Reuse HttpClient
using HttpClient httpClient = new();
// Get all releases
var releasesList = ReleasesJsonSchema.GetReleases(httpClient, GetRelease());
foreach (var release in releasesList)
{
// We expect only "vms" products
Debug.Assert(release.Product.Equals(Release.VmsProduct, StringComparison.OrdinalIgnoreCase));
// Set version
VersionInfo versionInfo = new();
Debug.Assert(!string.IsNullOrEmpty(release.Version));
versionInfo.SetVersion(release.Version);
// Add the label
AddLabel(versionInfo, release.GetLabel());
// Get the build number from the version
var buildNumber = versionInfo.GetBuildNumber();
// Get available packages for this release
var packageList = PackagesJsonSchema.GetPackages(httpClient, GetRelease(), buildNumber);
// Get the x64 and arm64 server ubuntu server packages
var packageX64 = packageList.Find(item => item.IsX64Server());
Debug.Assert(packageX64 != default(Package));
Debug.Assert(!string.IsNullOrEmpty(packageX64.File));
var packageArm64 = packageList.Find(item => item.IsArm64Server());
Debug.Assert(packageArm64 != default(Package));
Debug.Assert(!string.IsNullOrEmpty(packageArm64.File));
// Create the download URLs
// https://updates.networkoptix.com/{product}/{build}/{file}
versionInfo.UriX64 = $"https://updates.networkoptix.com/{GetRelease()}/{buildNumber}/{packageX64.File}";
versionInfo.UriArm64 = $"https://updates.networkoptix.com/{GetRelease()}/{buildNumber}/{packageArm64.File}";
// Verify and add to list
if (VerifyVersion(versionInfo))
Versions.Add(versionInfo);
}
// Make sure all labels are correct
VerifyLabels();
}
catch (Exception e) when (Log.Logger.LogAndHandle(e, MethodBase.GetCurrentMethod()?.Name))
{
// Log and rethrow
throw;
}
}
private bool VerifyVersion(VersionInfo versionInfo)
{
// Static rules:
// Ubuntu Jammy requires version 5.1 or later
if (versionInfo.CompareTo("5.1") >= 0)
return true;
Log.Logger.Warning("{Product}:{Version} : Ubuntu Jammy requires v5.1+", Product, versionInfo.Version);
return false;
}
public void AddLabel(VersionInfo versionInfo, VersionInfo.LabelType label)
{
// Ignore if label is None
if (label == VersionInfo.LabelType.None)
return;
// Does this label already exists in other versions
var existingVersion = Versions.Find(item => item.Labels.Contains(label));
if (existingVersion == default(VersionInfo))
{
// New label
versionInfo.Labels.Add(label);
return;
}
// Is this version larger than the other version
if (versionInfo.CompareTo(existingVersion) <= 0)
return;
Log.Logger.Warning("{Product}: Replacing {Label} from {ExistingVersion} to {NewVersion}", Product, label, existingVersion.Version, versionInfo.Version);
// Remove from other version and add to this version
existingVersion.Labels.Remove(label);
versionInfo.Labels.Add(label);
}
private VersionInfo? FindMissingLabel(VersionInfo.LabelType targetLabel, List<VersionInfo.LabelType> sourceLabels)
{
foreach (var label in sourceLabels)
{
// Find last matching item, must be sorted
var version = Versions.FindLast(item => item.Labels.Contains(label));
if (version != default(VersionInfo))
{
Log.Logger.Warning("{Product}: Using {SourceLabel} for {TargetLabel}", Product, label, targetLabel);
return version;
}
}
return default;
}
public void VerifyLabels()
{
// Sort by version number
Versions.Sort(new VersionInfoComparer());
// If no Latest label is set, use Stable or RC or Beta as Latest
if (!Versions.Any(item => item.Labels.Contains(VersionInfo.LabelType.Latest)))
{
var latest = FindMissingLabel(VersionInfo.LabelType.Latest, [VersionInfo.LabelType.Stable, VersionInfo.LabelType.RC, VersionInfo.LabelType.Beta]);
ArgumentNullException.ThrowIfNull(latest);
latest.Labels.Add(VersionInfo.LabelType.Latest);
}
// If no Stable label is set, use Latest as stable
if (!Versions.Any(item => item.Labels.Contains(VersionInfo.LabelType.Stable)))
{
var stable = FindMissingLabel(VersionInfo.LabelType.Stable, [VersionInfo.LabelType.Latest]);
ArgumentNullException.ThrowIfNull(stable);
stable.Labels.Add(VersionInfo.LabelType.Stable);
}
// Remove all versions without labels
Versions.RemoveAll(item => item.Labels.Count == 0);
// Sort by label
Versions.ForEach(item => item.Labels.Sort());
// Must have 1 Latest and 1 Stable label
ArgumentOutOfRangeException.ThrowIfNotEqual(Versions.Count(item => item.Labels.Contains(VersionInfo.LabelType.Latest)), 1);
ArgumentOutOfRangeException.ThrowIfNotEqual(Versions.Count(item => item.Labels.Contains(VersionInfo.LabelType.Stable)), 1);
// Must have no more than 1 Beta or RC labels
ArgumentOutOfRangeException.ThrowIfGreaterThan(Versions.Count(item => item.Labels.Contains(VersionInfo.LabelType.Beta)), 1);
ArgumentOutOfRangeException.ThrowIfGreaterThan(Versions.Count(item => item.Labels.Contains(VersionInfo.LabelType.RC)), 1);
}
public void LogInformation()
{
foreach (var version in Versions)
Log.Logger.Information("{Product}: Version: {Version}, Label: {Labels}, UriX64: {UriX64}, UriArm64: {UriArm64}", Product, version.Version, version.Labels, version.UriX64, version.UriArm64);
}
public void VerifyUrls()
{
try
{
using HttpClient httpClient = new();
foreach (var versionUri in Versions)
{
// Will throw on error
VerifyUrl(httpClient, versionUri.UriX64);
VerifyUrl(httpClient, versionUri.UriArm64);
}
}
catch (Exception e) when (Log.Logger.LogAndHandle(e, MethodBase.GetCurrentMethod()?.Name))
{
// Log and rethrow
throw;
}
}
private static void VerifyUrl(HttpClient httpClient, string url)
{
// Will throw on failure
// Get URL
var uri = new Uri(url);
Log.Logger.Information("Verifying Url: {Url}", url);
var httpResponse = httpClient.GetAsync(uri).Result;
httpResponse.EnsureSuccessStatusCode();
// Get filename from httpResponse or Uri path
string? fileName = null;
fileName ??= httpResponse.Content.Headers.ContentDisposition?.FileName;
fileName ??= Path.GetFileName(uri.LocalPath);
// Log details
Log.Logger.Information("File Name: {FileName}, File Size: {FileSize}, Last Modified: {LastModified}",
fileName,
httpResponse.Content.Headers.ContentLength,
httpResponse.Content.Headers.LastModified);
}
public void Verify()
{
// Match verification logic executed during GetVersions()
// Verify each version
List<VersionInfo> removeVersions = [];
foreach (var version in Versions.Where(version => !VerifyVersion(version)))
{
Log.Logger.Warning("{Product} : Removing {Version}", Product, version.Version);
removeVersions.Add(version);
}
Versions.RemoveAll(item => removeVersions.Contains(item));
// Verify labels
VerifyLabels();
}
}