Add project files.

This commit is contained in:
Jeff Leung 2024-01-04 20:39:36 -08:00
parent 6b91d168a3
commit 6fe03cfade
15 changed files with 463 additions and 0 deletions

30
.dockerignore Normal file
View File

@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

25
AS1024.GeoFeed.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AS1024.GeoFeed", "AS1024.GeoFeed\AS1024.GeoFeed.csproj", "{6292097C-7F35-45BB-B2B0-1918DF49FE7D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6292097C-7F35-45BB-B2B0-1918DF49FE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6292097C-7F35-45BB-B2B0-1918DF49FE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6292097C-7F35-45BB-B2B0-1918DF49FE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6292097C-7F35-45BB-B2B0-1918DF49FE7D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {66AEF076-9E39-4263-9719-4197027D1059}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
@AS1024.GeoFeed_HostAddress = http://localhost:5140
GET {{AS1024.GeoFeed_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,48 @@
using Microsoft.AspNetCore.Mvc;
using AS1024.GeoFeed.Interfaces;
using AS1024.GeoFeed.GeoFeedBuilder;
using Microsoft.Extensions.Caching.Memory;
using AS1024.GeoFeed.Models;
using System.Text;
namespace AS1024.GeoFeed.Controllers
{
[ApiController]
[Route("[controller]")]
[Route("[controller].csv")]
public class GeofeedController : ControllerBase
{
private readonly IGeoFeedProvider builder;
private readonly IMemoryCache memoryCache;
private const string GeoFeedCacheKey = "GeoFeedData";
public GeofeedController(IGeoFeedProvider builder,
IMemoryCache memoryCache) {
this.builder = builder;
this.memoryCache = memoryCache;
}
[HttpGet]
[Route("")]
public async Task<IActionResult> Get()
{
if (!memoryCache.TryGetValue(GeoFeedCacheKey, out List<IPGeoFeed>? feed))
{
feed = await builder.GetGeoFeedData();
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromHours(1));
memoryCache.Set(GeoFeedCacheKey, feed, cacheEntryOptions);
}
var csvContent = feed.ToGeoFeedCsv(); // Assuming ToGeoFeedCsv() returns a string in CSV format.
var contentBytes = Encoding.UTF8.GetBytes(csvContent);
var contentType = "text/csv";
return new FileContentResult(contentBytes, contentType)
{
FileDownloadName = "geofeed.csv"
};
}
}
}

24
AS1024.GeoFeed/Dockerfile Normal file
View File

@ -0,0 +1,24 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["AS1024.GeoFeed/AS1024.GeoFeed.csproj", "AS1024.GeoFeed/"]
RUN dotnet restore "./AS1024.GeoFeed/./AS1024.GeoFeed.csproj"
COPY . .
WORKDIR "/src/AS1024.GeoFeed"
RUN dotnet build "./AS1024.GeoFeed.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./AS1024.GeoFeed.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AS1024.GeoFeed.dll"]

View File

@ -0,0 +1,36 @@

using AS1024.GeoFeed.Interfaces;
using Microsoft.Extensions.Caching.Memory;
namespace AS1024.GeoFeed.GeoFeedBuilder
{
public class PreLoadGeoFeed : IHostedService
{
private readonly ILogger<PreLoadGeoFeed> logger;
private readonly IGeoFeedProvider provider;
private readonly IMemoryCache memoryCache;
private const string GeoFeedCacheKey = "GeoFeedData";
public PreLoadGeoFeed(ILogger<PreLoadGeoFeed> logger,
IGeoFeedProvider provider,
IMemoryCache memoryCache)
{
this.logger = logger;
this.provider = provider;
this.memoryCache = memoryCache;
}
async Task IHostedService.StartAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Preloading GeoFeed data in memory...");
var feed = await provider.GetGeoFeedData();
var cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromHours(1));
memoryCache.Set(GeoFeedCacheKey, feed, cacheEntryOptions);
}
Task IHostedService.StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,20 @@
using AS1024.GeoFeed.Models;
using System.Text;
namespace AS1024.GeoFeed.GeoFeedBuilder
{
public static class GeoFeedTools
{
public static string ToGeoFeedCsv(this List<IPGeoFeed> geoFeeds)
{
StringBuilder csvContent = new();
foreach (var feed in geoFeeds)
{
csvContent.AppendLine($"{feed.Prefix},{feed.GeolocCountry},{feed.GeolocRegion},{feed.GeolocCity}");
}
return csvContent.ToString();
}
}
}

View File

@ -0,0 +1,122 @@
using AS1024.GeoFeed.Interfaces;
using AS1024.GeoFeed.Models;
using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Web;
namespace AS1024.GeoFeed.GeoFeedBuilder
{
public class NetBoxGeoFeedProvider : IGeoFeedProvider
{
private readonly IConfiguration configuration;
private readonly ILogger<NetBoxGeoFeedProvider> logger;
private readonly IList<AddressFamily> addressFamilies = new List<AddressFamily>()
{
AddressFamily.InterNetwork,
AddressFamily.InterNetworkV6
};
private readonly IHttpClientFactory httpClientFactory;
string IGeoFeedProvider.GeoFeedProviderName => "netbox";
public NetBoxGeoFeedProvider(IConfiguration configuration,
ILogger<NetBoxGeoFeedProvider> logger,
IHttpClientFactory httpClientFactory)
{
this.configuration = configuration;
this.logger = logger;
this.httpClientFactory = httpClientFactory;
}
public async Task<List<IPGeoFeed>> GetGeoFeedData()
{
var geoFeed = new List<IPGeoFeed>();
using var client = httpClientFactory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", configuration["APIKey"]);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
foreach (var family in addressFamilies)
{
Uri uri = BuildNetBoxURI(family);
NetboxData? jsonData = null;
while (true)
{
logger.LogDebug($"Making request to {uri}...");
using var result = await client.GetAsync(uri);
if (result.IsSuccessStatusCode)
{
var stringResult = await result.Content.ReadAsStringAsync();
jsonData = JsonConvert.DeserializeObject<NetboxData>(stringResult);
if (jsonData?.Results == null || jsonData.Results.Count == 0)
{
break;
}
foreach (var data in jsonData.Results)
{
try
{
geoFeed.Add(new IPGeoFeed
{
Prefix = data.Prefix,
GeolocCity = data.CustomFields.GeolocCity,
GeolocRegion = data.CustomFields.GeolocRegion,
GeolocCountry = data.CustomFields.GeolocCountry,
GeolocHasLocation = data.CustomFields.GeolocHasLocation
});
}
catch (Exception ex)
{
logger.LogError(ex.ToString());
}
}
if (!string.IsNullOrEmpty(jsonData.Next))
{
uri = new Uri(jsonData.Next);
continue;
}
}
break;
}
}
return geoFeed;
}
protected Uri BuildNetBoxURI(AddressFamily family)
{
var queryParameters = HttpUtility.ParseQueryString(string.Empty);
queryParameters["cf_geoloc_has_location"] = "true";
queryParameters["limit"] = "5";
switch (family)
{
case AddressFamily.InterNetwork:
queryParameters["mask_length__lte"] = "24";
queryParameters["family"] = "4";
break;
case AddressFamily.InterNetworkV6:
queryParameters["mask_length__lte"] = "48";
queryParameters["family"] = "6";
break;
}
var endUrl = new UriBuilder
{
Path = "api/ipam/prefixes/",
Query = queryParameters.ToString(),
Host = configuration["NetBoxHost"],
Scheme = "https"
};
return endUrl.Uri;
}
}
}

View File

@ -0,0 +1,10 @@
using AS1024.GeoFeed.Models;
namespace AS1024.GeoFeed.Interfaces
{
public interface IGeoFeedProvider
{
public string GeoFeedProviderName { get; }
public Task<List<IPGeoFeed>> GetGeoFeedData();
}
}

View File

@ -0,0 +1,35 @@
using Newtonsoft.Json;
namespace AS1024.GeoFeed.Models
{
public class NetboxData
{
public List<Result>? Results { get; set; }
public string? Next { get; set; }
public string? Previous { get; set; }
}
public class Result
{
public string? Prefix { get; set; }
[JsonProperty("custom_fields")]
public CustomFields? CustomFields { get; set; }
}
public class CustomFields
{
[JsonProperty("geoloc_city")]
public string? GeolocCity { get; set; }
[JsonProperty("geoloc_country")]
public string? GeolocCountry { get; set; }
[JsonProperty("geoloc_has_location")]
public bool? GeolocHasLocation { get; set; }
[JsonProperty("geoloc_region")]
public string? GeolocRegion { get; set; }
}
public class IPGeoFeed : CustomFields {
public string? Prefix { get; set; }
}
}

32
AS1024.GeoFeed/Program.cs Normal file
View File

@ -0,0 +1,32 @@
using AS1024.GeoFeed.GeoFeedBuilder;
using AS1024.GeoFeed.Interfaces;
namespace AS1024.GeoFeed
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHostedService<PreLoadGeoFeed>();
builder.Services.AddTransient<IGeoFeedProvider, NetBoxGeoFeedProvider>();
builder.Services.AddHttpClient();
builder.Services.AddMemoryCache();
// Add services to the container.
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
}

View File

@ -0,0 +1,40 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "geofeed",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5140"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "geofeed",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/weatherforecast",
"environmentVariables": {
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:30039",
"sslPort": 0
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,11 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"APIKey": "",
"NetBoxHost": ""
}