Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New: Invalid cache webhook endpoint #29

Merged
merged 4 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions src/ServarrAPI/Cloudflare/CloudflareProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public interface ICloudflareProxy

public class CloudflareProxy : ICloudflareProxy
{
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
private static readonly JsonSerializerOptions JsonOptions = new ()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
Expand Down Expand Up @@ -60,6 +60,8 @@ public async Task InvalidateBranches(IEnumerable<string> branches)
{
if (!_enabled)
{
_logger.LogTrace("CloudflareProxy is disabled");

return;
}

Expand All @@ -68,14 +70,15 @@ public async Task InvalidateBranches(IEnumerable<string> branches)

while (paged.Any())
{
_logger.LogTrace($"Invalidating page {page} with {paged.Count()} ids");
_logger.LogTrace("Invalidating page {0} with {1} ids", page, paged.Count);

var files = paged
.SelectMany(x => new[]
{
$"{_config.BaseUrl}/update/{x}",
$"{_config.BaseUrl}/update/{x}/changes",
$"{_config.BaseUrl}/update/{x}/updatefile"
})
{
$"{_config.BaseUrl}/update/{x}",
$"{_config.BaseUrl}/update/{x}/changes",
$"{_config.BaseUrl}/update/{x}/updatefile"
})
.ToList();

var payload = new CloudflareInvalidationRequest
Expand Down Expand Up @@ -108,7 +111,7 @@ public async Task InvalidateBranches(IEnumerable<string> branches)
_logger.LogError(e, "Invalidation failed");
}

_logger.LogTrace($"Invalidation failed, retrying");
_logger.LogTrace("Invalidation failed, retrying");

retries--;
}
Expand All @@ -117,9 +120,9 @@ public async Task InvalidateBranches(IEnumerable<string> branches)
}
}

private IEnumerable<string> GetPage(IEnumerable<string> items, int page)
private List<string> GetPage(IEnumerable<string> items, int page)
{
return items.Skip(page * 10).Take(10);
return items.Skip(page * 10).Take(10).ToList();
}

private HttpRequestMessage GetInvalidationMessage(CloudflareInvalidationRequest payload)
Expand Down
23 changes: 23 additions & 0 deletions src/ServarrAPI/Controllers/WebhookController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using ServarrAPI.Cloudflare;
using ServarrAPI.Release;
using ServarrAPI.Release.Azure;
using ServarrAPI.Release.Github;
Expand All @@ -15,13 +16,16 @@ public class WebhookController
private readonly Config _config;
private readonly IBackgroundTaskQueue _queue;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ICloudflareProxy _cloudflare;

public WebhookController(IBackgroundTaskQueue queue,
IServiceScopeFactory serviceScopeFactory,
ICloudflareProxy cloudflare,
IOptions<Config> optionsConfig)
{
_queue = queue;
_serviceScopeFactory = serviceScopeFactory;
_cloudflare = cloudflare;
_config = optionsConfig.Value;
}

Expand Down Expand Up @@ -58,5 +62,24 @@ public string Refresh([FromQuery] string source, [FromQuery(Name = "api_key")] s

return "Thank you.";
}

[Route("branch/{branch}/refresh")]
[HttpGet]
public async Task<string> InvalidateCache([FromQuery(Name = "api_key")] string apiKey, [FromRoute(Name = "branch")] string branch)
{
if (!_config.ApiKey.Equals(apiKey))
{
return "No, thank you.";
}

if (string.IsNullOrWhiteSpace(branch))
{
return "Unknown branch.";
}

await _cloudflare.InvalidateBranches(new[] { branch }).ConfigureAwait(false);

return "Thank you.";
}
}
}
6 changes: 3 additions & 3 deletions src/ServarrAPI/Datastore/Migration/002_break_up_version.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ protected override void MainDbUpgrade()
// Create a sortable integer for version that can easily be compared against a max version,
// supports any major, minor to 99, patch to 99, builds up to 999,999.
Execute.Sql("UPDATE update SET intversion = " +
"((string_to_array(version, '.')::int[])[1] * 10000000000) + " +
"((string_to_array(version, '.')::int[])[2] * 100000000) + " +
"((string_to_array(version, '.')::int[])[3] * 1000000) + " +
"((string_to_array(version, '.')::int[])[1] * 10000000000L) + " +
"((string_to_array(version, '.')::int[])[2] * 100000000L) + " +
"((string_to_array(version, '.')::int[])[3] * 1000000L) + " +
"((string_to_array(version, '.')::int[])[4])");
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/ServarrAPI/Extensions/VersionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public static class VersionExtensions
{
public static long ToIntVersion(this Version version)
{
return (version.Major * 10000000000) + (version.Minor * 100000000) + (version.Build * 1000000) + version.Revision;
return (version.Major * 10000000000L) + (version.Minor * 100000000L) + (version.Build * 1000000L) + version.Revision;
}
}
}
62 changes: 35 additions & 27 deletions src/ServarrAPI/Release/Azure/AzureReleaseSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,40 +247,48 @@ private async Task ProcessFile(AzureFile file, string branch, int updateId)

// Calculate the hash of the zip file.
var releaseFileName = Path.GetFileName(file.Path);
var releaseZip = Path.Combine(_config.DataDirectory, branch, releaseFileName);
string releaseHash;
var releaseZip = Path.Combine(_config.DataDirectory, branch.ToLowerInvariant(), releaseFileName);

if (!File.Exists(releaseZip))
try
{
Directory.CreateDirectory(Path.GetDirectoryName(releaseZip));
if (!File.Exists(releaseZip))
{
Directory.CreateDirectory(Path.GetDirectoryName(releaseZip));

using var fileStream = File.OpenWrite(releaseZip);
using var artifactStream = await _httpClient.GetStreamAsync(file.Url).ConfigureAwait(false);
await artifactStream.CopyToAsync(fileStream).ConfigureAwait(false);
}
await using var fileStream = File.OpenWrite(releaseZip);
await using var artifactStream = await _httpClient.GetStreamAsync(file.Url).ConfigureAwait(false);
await artifactStream.CopyToAsync(fileStream).ConfigureAwait(false);
}

using (var stream = File.OpenRead(releaseZip))
{
using var sha = SHA256.Create();
releaseHash = BitConverter.ToString(sha.ComputeHash(stream)).Replace("-", "").ToLower();
}
string releaseHash;
await using (var stream = File.OpenRead(releaseZip))
using (var sha = SHA256.Create())
{
releaseHash = BitConverter.ToString(await sha.ComputeHashAsync(stream)).Replace("-", "").ToLowerInvariant();
}

File.Delete(releaseZip);
// Add to database.
var updateFile = new UpdateFileEntity
{
UpdateId = updateId,
OperatingSystem = operatingSystem.Value,
Architecture = arch,
Runtime = runtime,
Filename = releaseFileName,
Url = file.Url,
Hash = releaseHash,
Installer = installer
};

// Add to database.
var updateFile = new UpdateFileEntity
await _updateFileService.Insert(updateFile).ConfigureAwait(false);
}
finally
{
UpdateId = updateId,
OperatingSystem = operatingSystem.Value,
Architecture = arch,
Runtime = runtime,
Filename = releaseFileName,
Url = file.Url,
Hash = releaseHash,
Installer = installer
};

await _updateFileService.Insert(updateFile).ConfigureAwait(false);
if (File.Exists(releaseZip))
{
File.Delete(releaseZip);
}
}
}
}
}
92 changes: 52 additions & 40 deletions src/ServarrAPI/Release/Github/GithubReleaseSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,28 @@ protected override async Task<List<string>> DoFetchReleasesAsync()
var updated = new HashSet<string>();

var githubOrg = _config.GithubOrg ?? _config.Project;
var releases = (await _gitHubClient.Repository.Release.GetAll(githubOrg, _config.Project)).ToArray();
var validReleases = releases
.Where(r => r.TagName.StartsWith("v") && VersionUtil.IsValid(r.TagName.Substring(1)))
var releases = await _gitHubClient.Repository.Release.GetAll(
githubOrg,
_config.Project,
new ApiOptions
{
PageSize = 100,
PageCount = 1
})
.ConfigureAwait(false);

var validReleases = releases.ToArray()
.Where(r => r.PublishedAt.HasValue && r.TagName.StartsWith("v") && VersionUtil.IsValid(r.TagName.Substring(1)))
.Take(3)
.Reverse();
.Reverse()
.ToArray();

foreach (var release in validReleases)
{
// Check if release has been published.
if (!release.PublishedAt.HasValue)
{
continue;
}

var version = release.TagName.Substring(1);

// determine the branch
var branch = release.Assets.Any(a => a.Name.StartsWith(string.Format("{0}.master", _config.Project))) ? "master" : "develop";
var branch = release.Assets.Any(a => a.Name.StartsWith($"{_config.Project}.master")) ? "master" : "develop";

if (await ProcessRelease(release, branch, version))
{
Expand Down Expand Up @@ -135,7 +139,7 @@ private async Task<bool> ProcessRelease(Octokit.Release release, string branch,
return isNewRelease;
}

private async Task ProcessAsset(Octokit.ReleaseAsset releaseAsset, string branch, int updateId)
private async Task ProcessAsset(ReleaseAsset releaseAsset, string branch, int updateId)
{
var operatingSystem = Parser.ParseOS(releaseAsset.Name);
if (!operatingSystem.HasValue)
Expand All @@ -148,40 +152,48 @@ private async Task ProcessAsset(Octokit.ReleaseAsset releaseAsset, string branch
var installer = Parser.ParseInstaller(releaseAsset.Name);

// Calculate the hash of the zip file.
var releaseZip = Path.Combine(_config.DataDirectory, branch.ToString().ToLower(), releaseAsset.Name);
string releaseHash;
var releaseZip = Path.Combine(_config.DataDirectory, branch.ToLowerInvariant(), releaseAsset.Name);

if (!File.Exists(releaseZip))
try
{
Directory.CreateDirectory(Path.GetDirectoryName(releaseZip));

using var fileStream = File.OpenWrite(releaseZip);
using var artifactStream = await _httpClient.GetStreamAsync(releaseAsset.BrowserDownloadUrl);
await artifactStream.CopyToAsync(fileStream);
}
if (!File.Exists(releaseZip))
{
Directory.CreateDirectory(Path.GetDirectoryName(releaseZip));

using (var stream = File.OpenRead(releaseZip))
using (var sha = SHA256.Create())
{
releaseHash = BitConverter.ToString(sha.ComputeHash(stream)).Replace("-", "").ToLower();
}
await using var fileStream = File.OpenWrite(releaseZip);
await using var artifactStream = await _httpClient.GetStreamAsync(releaseAsset.BrowserDownloadUrl);
await artifactStream.CopyToAsync(fileStream);
}

File.Delete(releaseZip);
string releaseHash;
await using (var stream = File.OpenRead(releaseZip))
using (var sha = SHA256.Create())
{
releaseHash = BitConverter.ToString(await sha.ComputeHashAsync(stream)).Replace("-", "").ToLowerInvariant();
}

// Add to database.
var updateFile = new UpdateFileEntity
// Add to database.
var updateFile = new UpdateFileEntity
{
UpdateId = updateId,
OperatingSystem = operatingSystem.Value,
Architecture = arch,
Runtime = runtime,
Filename = releaseAsset.Name,
Url = releaseAsset.BrowserDownloadUrl,
Hash = releaseHash,
Installer = installer
};

await _updateFileService.Insert(updateFile).ConfigureAwait(false);
}
finally
{
UpdateId = updateId,
OperatingSystem = operatingSystem.Value,
Architecture = arch,
Runtime = runtime,
Filename = releaseAsset.Name,
Url = releaseAsset.BrowserDownloadUrl,
Hash = releaseHash,
Installer = installer
};

await _updateFileService.Insert(updateFile).ConfigureAwait(false);
if (File.Exists(releaseZip))
{
File.Delete(releaseZip);
}
}
}
}
}
5 changes: 2 additions & 3 deletions src/ServarrAPI/Release/ReleaseService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
Expand Down Expand Up @@ -46,7 +45,7 @@ public async Task UpdateReleasesAsync(Type releaseSource)

var updatedBranches = await releaseSourceInstance.StartFetchReleasesAsync().ConfigureAwait(false);

if (updatedBranches != null && updatedBranches.Count > 0)
if (updatedBranches is { Count: > 0 })
{
foreach (var branch in updatedBranches)
{
Expand Down Expand Up @@ -74,7 +73,7 @@ private async Task CallTriggers(string branch)
request.Headers.Add("Authorization", "Bearer " + trigger.AuthToken);
}

string json = JsonSerializer.Serialize(new { Application = _config.Project, Branch = branch }, JsonOptions);
var json = JsonSerializer.Serialize(new { Application = _config.Project, Branch = branch }, JsonOptions);
var httpContent = new StringContent(json, Encoding.UTF8, "application/json");
request.Content = httpContent;

Expand Down