Compare commits
57 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
5cad97c9ac | |
|
|
cc61b02959 | |
|
|
0dfe68656d | |
|
|
6544dc5a3d | |
|
|
08e518c340 | |
|
|
e82d198ee7 | |
|
|
4c6a9e6dec | |
|
|
feb1326cee | |
|
|
626ab99888 | |
|
|
9c8189bc33 | |
|
|
956aa8b3b0 | |
|
|
7d5b2a7aac | |
|
|
5f089fb01f | |
|
|
af3cc81180 | |
|
|
e0189381f1 | |
|
|
3a56e2426e | |
|
|
0c3edd64f3 | |
|
|
2d95fcb1a1 | |
|
|
7c952c134a | |
|
|
7721dfb669 | |
|
|
c1ed05a335 | |
|
|
690a117ffd | |
|
|
9dfbded5b8 | |
|
|
7b7d422890 | |
|
|
2d6083f530 | |
|
|
0e56d778bc | |
|
|
8523ddd60f | |
|
|
83826cc930 | |
|
|
cc1717e4a3 | |
|
|
48932ec2a5 | |
|
|
42aacf497c | |
|
|
e8152186c1 | |
|
|
43b34143a3 | |
|
|
8ed021754c | |
|
|
a44b4f9421 | |
|
|
810993cbf3 | |
|
|
8d6999c95c | |
|
|
704d6b24dc | |
|
|
81d01f28a3 | |
|
|
bdd247e6e0 | |
|
|
52708bd2c2 | |
|
|
9c5fbc314f | |
|
|
1f3778095c | |
|
|
2d5c318689 | |
|
|
dd643ab8ab | |
|
|
2d68115f25 | |
|
|
020db780a6 | |
|
|
5fec5f4f92 | |
|
|
c43b218974 | |
|
|
c18e486750 | |
|
|
9974bb5f0c | |
|
|
37841db663 | |
|
|
e974635cfb | |
|
|
2fa8f32f44 | |
|
|
6e9d58c6e3 | |
|
|
ade2861395 | |
|
|
4ea9f2ca7f |
|
|
@ -0,0 +1,20 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\AS1024.GeoFeed.Models\AS1024.GeoFeed.Models.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
|
||||||
|
{
|
||||||
|
public class GeoFeedCacheDbContext : DbContext
|
||||||
|
{
|
||||||
|
public GeoFeedCacheDbContext(DbContextOptions options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual DbSet<GeoFeedCacheEntry> GeoFeedCacheEntries { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
using AS1024.GeoFeed.Models;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
|
||||||
|
{
|
||||||
|
public class GeoFeedCacheEntry : IPGeoFeed
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
|
||||||
|
|
||||||
|
{
|
||||||
|
public class GeoFeedDesignTimeMigration : IDesignTimeDbContextFactory<GeoFeedCacheDbContext>
|
||||||
|
{
|
||||||
|
public GeoFeedCacheDbContext CreateDbContext(string[] args)
|
||||||
|
{
|
||||||
|
var builder = new DbContextOptionsBuilder<GeoFeedCacheDbContext>();
|
||||||
|
builder.UseSqlite("Data Source=migratedb.db");
|
||||||
|
return new GeoFeedCacheDbContext(builder.Options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
using AS1024.GeoFeed.Core.Interfaces;
|
||||||
|
using AS1024.GeoFeed.Core.Tools;
|
||||||
|
using AS1024.GeoFeed.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache
|
||||||
|
{
|
||||||
|
public class GeoFeedSqliteCache : IGeoFeedPersistentCacheProvider
|
||||||
|
{
|
||||||
|
protected readonly GeoFeedCacheDbContext dbContext;
|
||||||
|
private readonly IGeoFeedProvider feedProvider;
|
||||||
|
|
||||||
|
public GeoFeedSqliteCache(GeoFeedCacheDbContext geoFeedCacheDb,
|
||||||
|
IHost host,
|
||||||
|
IGeoFeedProvider provider)
|
||||||
|
{
|
||||||
|
dbContext = geoFeedCacheDb;
|
||||||
|
feedProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ProviderName => "sqlite";
|
||||||
|
|
||||||
|
public async Task<bool> CacheGeoFeed(IList<IPGeoFeed> pGeoFeeds)
|
||||||
|
{
|
||||||
|
await DBContextMigrate();
|
||||||
|
List<GeoFeedCacheEntry> geoFeedCacheEntry = [];
|
||||||
|
|
||||||
|
var results = pGeoFeeds.ToList();
|
||||||
|
|
||||||
|
results.ForEach(x =>
|
||||||
|
{
|
||||||
|
geoFeedCacheEntry.Add(new()
|
||||||
|
{
|
||||||
|
Prefix = x.Prefix,
|
||||||
|
GeolocCity = x.GeolocCity,
|
||||||
|
GeolocCountry = x.GeolocCountry,
|
||||||
|
GeolocHasLocation = x.GeolocHasLocation,
|
||||||
|
GeolocPostalCode = x.GeolocPostalCode,
|
||||||
|
GeolocRegion = x.GeolocRegion
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbContext.GeoFeedCacheEntries.Any())
|
||||||
|
{
|
||||||
|
dbContext.GeoFeedCacheEntries.RemoveRange(dbContext.GeoFeedCacheEntries.ToArray());
|
||||||
|
}
|
||||||
|
await dbContext.AddRangeAsync(geoFeedCacheEntry);
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetGeoFeed()
|
||||||
|
{
|
||||||
|
var results =
|
||||||
|
dbContext.GeoFeedCacheEntries.ToList();
|
||||||
|
List<IPGeoFeed> cachedData = [];
|
||||||
|
results.ForEach(cachedData.Add);
|
||||||
|
|
||||||
|
return cachedData.ToGeoFeedCsv(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DBContextMigrate()
|
||||||
|
{
|
||||||
|
if (dbContext.Database.GetPendingMigrations().Any())
|
||||||
|
{
|
||||||
|
await dbContext.Database.MigrateAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
AS1024.GeoFeed.Core.SqliteGeoFeedCache/Migrations/20240114002908_InitialMigration.Designer.cs
generated
Normal file
54
AS1024.GeoFeed.Core.SqliteGeoFeedCache/Migrations/20240114002908_InitialMigration.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.SqliteGeoFeedCache.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(GeoFeedCacheDbContext))]
|
||||||
|
[Migration("20240114002908_InitialMigration")]
|
||||||
|
partial class InitialMigration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
|
||||||
|
|
||||||
|
modelBuilder.Entity("AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache.GeoFeedCacheEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("GeolocCity")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("GeolocCountry")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool?>("GeolocHasLocation")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("GeolocPostalCode")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("GeolocRegion")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Prefix")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("GeoFeedCacheEntries");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.SqliteGeoFeedCache.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class InitialMigration : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "GeoFeedCacheEntries",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
GeolocCity = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
GeolocCountry = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
GeolocHasLocation = table.Column<bool>(type: "INTEGER", nullable: true),
|
||||||
|
GeolocRegion = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
GeolocPostalCode = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
Prefix = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_GeoFeedCacheEntries", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "GeoFeedCacheEntries");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.SqliteGeoFeedCache.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(GeoFeedCacheDbContext))]
|
||||||
|
partial class GeoFeedCacheDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
|
||||||
|
|
||||||
|
modelBuilder.Entity("AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache.GeoFeedCacheEntry", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("GeolocCity")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("GeolocCountry")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool?>("GeolocHasLocation")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("GeolocPostalCode")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("GeolocRegion")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Prefix")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("GeoFeedCacheEntries");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
using AS1024.GeoFeed.Core.Interfaces;
|
||||||
|
using AS1024.GeoFeed.Core.Tools;
|
||||||
|
using AS1024.GeoFeed.Models;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.WebLogic
|
||||||
|
{
|
||||||
|
public class GeoFeedReturn
|
||||||
|
{
|
||||||
|
private const string GeoFeedCacheKey = "GeoFeedData";
|
||||||
|
private const string GeoFeedMimeTypeReturn = "text/csv";
|
||||||
|
private const string GeoFeedFileName = "geofeed.csv";
|
||||||
|
private readonly IGeoFeedProvider provider;
|
||||||
|
private readonly ILogger<GeoFeedReturn> logger;
|
||||||
|
private readonly IGeoFeedPersistentCacheProvider cacheProvider;
|
||||||
|
private readonly IMemoryCache memoryCache;
|
||||||
|
private readonly IWebHostEnvironment environment;
|
||||||
|
|
||||||
|
public GeoFeedReturn(IGeoFeedProvider provider,
|
||||||
|
ILogger<GeoFeedReturn> logger,
|
||||||
|
IGeoFeedPersistentCacheProvider cacheProvider,
|
||||||
|
IMemoryCache memoryCache,
|
||||||
|
IWebHostEnvironment environment)
|
||||||
|
{
|
||||||
|
this.provider = provider;
|
||||||
|
this.logger = logger;
|
||||||
|
this.cacheProvider = cacheProvider;
|
||||||
|
this.memoryCache = memoryCache;
|
||||||
|
this.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> GetGeoFeed()
|
||||||
|
{
|
||||||
|
bool isCached = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!memoryCache.TryGetValue(GeoFeedCacheKey, out List<IPGeoFeed>? feed))
|
||||||
|
{
|
||||||
|
isCached = false;
|
||||||
|
feed = await provider.GetGeoFeedDataAsync();
|
||||||
|
if (environment.IsProduction())
|
||||||
|
{
|
||||||
|
MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions()
|
||||||
|
.SetSlidingExpiration(TimeSpan.FromMinutes(15));
|
||||||
|
memoryCache.Set(GeoFeedCacheKey, feed, cacheEntryOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Results.File(Encoding.UTF8.GetBytes(feed.ToGeoFeedCsv(true, isCached)),
|
||||||
|
GeoFeedMimeTypeReturn,
|
||||||
|
GeoFeedFileName);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
logger.LogWarning($"Temporary failure of retrieving GeoData from upstream. {ex}");
|
||||||
|
string geoFeedData = cacheProvider.GetGeoFeed();
|
||||||
|
|
||||||
|
return Results.File(Encoding.UTF8.GetBytes(geoFeedData),
|
||||||
|
GeoFeedMimeTypeReturn,
|
||||||
|
GeoFeedFileName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError($"Error: {ex}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Results.NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"AS1024.GeoFeed.Core.WebLogic": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"applicationUrl": "https://localhost:54455;http://localhost:54456"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\AS1024.GeoFeed.Models\AS1024.GeoFeed.Models.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
using AS1024.GeoFeed.Core.Interfaces;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.CacheService
|
||||||
|
{
|
||||||
|
public class GeoFeedCacheService : IHostedService
|
||||||
|
{
|
||||||
|
private readonly ILogger<GeoFeedCacheService> logger;
|
||||||
|
private readonly IGeoFeedProvider feedProvider;
|
||||||
|
private readonly IHost host;
|
||||||
|
|
||||||
|
public GeoFeedCacheService(ILogger<GeoFeedCacheService> logger,
|
||||||
|
IGeoFeedProvider feedProvider,
|
||||||
|
IHost host)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
this.feedProvider = feedProvider;
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_ = StartPerioidicSync(cancellationToken);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> StartPerioidicSync(CancellationToken Token)
|
||||||
|
{
|
||||||
|
while (!Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var scope = host.Services.CreateScope();
|
||||||
|
|
||||||
|
var persistentCacheProvider =
|
||||||
|
scope.ServiceProvider.GetRequiredService<IGeoFeedPersistentCacheProvider>();
|
||||||
|
|
||||||
|
var results = await feedProvider.GetGeoFeedDataAsync();
|
||||||
|
await persistentCacheProvider.CacheGeoFeed(results);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
logger.LogWarning("On disk cache failed to run. Waiting on 30 minutes before retry...");
|
||||||
|
}
|
||||||
|
await Task.Delay(TimeSpan.FromMinutes(30));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
using AS1024.GeoFeed.Core.Interfaces;
|
||||||
|
using AS1024.GeoFeed.Core.Tools;
|
||||||
|
using AS1024.GeoFeed.Models;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.CacheService
|
||||||
|
{
|
||||||
|
public class GeoFeedLocalFileCache : IGeoFeedPersistentCacheProvider
|
||||||
|
{
|
||||||
|
private readonly IConfiguration configuration;
|
||||||
|
private readonly ILogger<GeoFeedLocalFileCache> logger;
|
||||||
|
|
||||||
|
public GeoFeedLocalFileCache(IConfiguration _configuration,
|
||||||
|
ILogger<GeoFeedLocalFileCache> logger)
|
||||||
|
{
|
||||||
|
configuration = _configuration;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
string IGeoFeedPersistentCacheProvider.ProviderName => "file";
|
||||||
|
|
||||||
|
Task<bool> IGeoFeedPersistentCacheProvider.CacheGeoFeed(IList<IPGeoFeed> pGeoFeeds)
|
||||||
|
{
|
||||||
|
string? tempPath = GetTempPath();
|
||||||
|
logger.LogInformation($"Writing geofeed data to: {tempPath}");
|
||||||
|
File.WriteAllText(tempPath, pGeoFeeds.ToList().
|
||||||
|
ToGeoFeedCsv());
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetTempPath()
|
||||||
|
{
|
||||||
|
string? tempPath = Path.Combine(Path.GetTempPath(), "geoFeedCache.csv");
|
||||||
|
|
||||||
|
logger.LogInformation($"Getting GeoFeed data from: {tempPath}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(configuration["TempCache"]))
|
||||||
|
tempPath = configuration["TempCache"];
|
||||||
|
return tempPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
string IGeoFeedPersistentCacheProvider.GetGeoFeed()
|
||||||
|
{
|
||||||
|
return File.ReadAllText(GetTempPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,23 @@
|
||||||
|
using AS1024.GeoFeed.Core.Interfaces;
|
||||||
using AS1024.GeoFeed.Interfaces;
|
using AS1024.GeoFeed.Models;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace AS1024.GeoFeed.GeoFeedBuilder
|
namespace AS1024.GeoFeed.Core.GeoFeedPreloader
|
||||||
{
|
{
|
||||||
public class PreLoadGeoFeed : IHostedService
|
public class PreLoadGeoFeed : IHostedService
|
||||||
{
|
{
|
||||||
private readonly ILogger<PreLoadGeoFeed> logger;
|
private readonly ILogger<PreLoadGeoFeed> logger;
|
||||||
private readonly IGeoFeedProvider provider;
|
private readonly IGeoFeedProvider provider;
|
||||||
private readonly IMemoryCache memoryCache;
|
private readonly IMemoryCache memoryCache;
|
||||||
private readonly IWebHostEnvironment environment;
|
private readonly IHostEnvironment environment;
|
||||||
private const string GeoFeedCacheKey = "GeoFeedData";
|
private const string GeoFeedCacheKey = "GeoFeedData";
|
||||||
|
|
||||||
public PreLoadGeoFeed(ILogger<PreLoadGeoFeed> logger,
|
public PreLoadGeoFeed(ILogger<PreLoadGeoFeed> logger,
|
||||||
IGeoFeedProvider provider,
|
IGeoFeedProvider provider,
|
||||||
IMemoryCache memoryCache,
|
IMemoryCache memoryCache,
|
||||||
IWebHostEnvironment environment)
|
IHostEnvironment environment)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
|
|
@ -29,7 +31,8 @@ namespace AS1024.GeoFeed.GeoFeedBuilder
|
||||||
{
|
{
|
||||||
if (environment.IsProduction())
|
if (environment.IsProduction())
|
||||||
await StartPreLoad();
|
await StartPreLoad();
|
||||||
} catch (Exception ex)
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogWarning($"Failed to preload, exception settings below:\n{ex}");
|
logger.LogWarning($"Failed to preload, exception settings below:\n{ex}");
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +41,7 @@ namespace AS1024.GeoFeed.GeoFeedBuilder
|
||||||
private async Task StartPreLoad()
|
private async Task StartPreLoad()
|
||||||
{
|
{
|
||||||
logger.LogInformation("Preloading GeoFeed data in memory...");
|
logger.LogInformation("Preloading GeoFeed data in memory...");
|
||||||
List<Models.IPGeoFeed> feed = await provider.GetGeoFeedData();
|
List<IPGeoFeed> feed = await provider.GetGeoFeedDataAsync();
|
||||||
MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(45));
|
MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(45));
|
||||||
memoryCache.Set(GeoFeedCacheKey, feed, cacheEntryOptions);
|
memoryCache.Set(GeoFeedCacheKey, feed, cacheEntryOptions);
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
using AS1024.GeoFeed.Core.Interfaces;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.GeoFeedProviders
|
||||||
|
{
|
||||||
|
public class NetBoxGeoFeedProvider : NetBoxGeoFeedProviderBase, IGeoFeedProvider
|
||||||
|
{
|
||||||
|
public NetBoxGeoFeedProvider(IConfiguration configuration, ILogger<NetBoxGeoFeedProvider> logger, IHttpClientFactory httpClientFactory)
|
||||||
|
: base(configuration, logger, httpClientFactory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,28 @@
|
||||||
using AS1024.GeoFeed.Interfaces;
|
using AS1024.GeoFeed.Core.Interfaces;
|
||||||
using AS1024.GeoFeed.Models;
|
using AS1024.GeoFeed.Models;
|
||||||
using System.Text.Json;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
|
|
||||||
namespace AS1024.GeoFeed.GeoFeedBuilder
|
namespace AS1024.GeoFeed.Core.GeoFeedProviders
|
||||||
{
|
{
|
||||||
public class NetBoxGeoFeedProvider : IGeoFeedProvider
|
public abstract class NetBoxGeoFeedProviderBase : IGeoFeedProvider
|
||||||
{
|
{
|
||||||
private readonly IConfiguration configuration;
|
protected readonly IConfiguration configuration;
|
||||||
private readonly ILogger<NetBoxGeoFeedProvider> logger;
|
protected readonly ILogger<NetBoxGeoFeedProvider> logger;
|
||||||
private readonly IList<AddressFamily> addressFamilies = new List<AddressFamily>()
|
protected readonly IList<AddressFamily> addressFamilies = new List<AddressFamily>()
|
||||||
{
|
{
|
||||||
AddressFamily.InterNetwork,
|
AddressFamily.InterNetwork,
|
||||||
AddressFamily.InterNetworkV6
|
AddressFamily.InterNetworkV6
|
||||||
};
|
};
|
||||||
private readonly IHttpClientFactory httpClientFactory;
|
protected readonly IHttpClientFactory httpClientFactory;
|
||||||
|
|
||||||
string IGeoFeedProvider.GeoFeedProviderName => "netbox";
|
string IGeoFeedProvider.GeoFeedProviderName => "netbox";
|
||||||
|
|
||||||
public NetBoxGeoFeedProvider(IConfiguration configuration,
|
public NetBoxGeoFeedProviderBase(IConfiguration configuration,
|
||||||
ILogger<NetBoxGeoFeedProvider> logger,
|
ILogger<NetBoxGeoFeedProvider> logger,
|
||||||
IHttpClientFactory httpClientFactory)
|
IHttpClientFactory httpClientFactory)
|
||||||
{
|
{
|
||||||
|
|
@ -29,9 +31,9 @@ namespace AS1024.GeoFeed.GeoFeedBuilder
|
||||||
this.httpClientFactory = httpClientFactory;
|
this.httpClientFactory = httpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<IPGeoFeed>> GetGeoFeedData()
|
public async Task<List<IPGeoFeed>> GetGeoFeedDataAsync()
|
||||||
{
|
{
|
||||||
List<IPGeoFeed> geoFeed = new List<IPGeoFeed>();
|
List<IPGeoFeed> geoFeed = [];
|
||||||
using HttpClient client = httpClientFactory.CreateClient();
|
using HttpClient client = httpClientFactory.CreateClient();
|
||||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", configuration["APIKey"]);
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", configuration["APIKey"]);
|
||||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
|
@ -46,45 +48,44 @@ namespace AS1024.GeoFeed.GeoFeedBuilder
|
||||||
logger.LogDebug($"Making request to {uri}...");
|
logger.LogDebug($"Making request to {uri}...");
|
||||||
|
|
||||||
using HttpResponseMessage result = await client.GetAsync(uri);
|
using HttpResponseMessage result = await client.GetAsync(uri);
|
||||||
if (result.IsSuccessStatusCode)
|
if (!result.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
string stringResult = await result.Content.ReadAsStringAsync();
|
break;
|
||||||
jsonData = JsonSerializer.Deserialize<NetboxData>(stringResult, new JsonSerializerOptions {
|
}
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
var stringResult = await result.Content.ReadAsStreamAsync();
|
||||||
});
|
jsonData = DeserializeJsonData(stringResult);
|
||||||
|
|
||||||
if (jsonData?.Results == null || jsonData.Results.Count == 0)
|
if (jsonData?.Results == null || jsonData.Results.Count == 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Result data in jsonData.Results)
|
foreach (Result data in jsonData.Results)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
geoFeed.Add(new IPGeoFeed
|
||||||
{
|
{
|
||||||
geoFeed.Add(new IPGeoFeed
|
Prefix = data.Prefix,
|
||||||
{
|
GeolocCity = data.CustomFields.GeolocCity,
|
||||||
Prefix = data.Prefix,
|
GeolocRegion = data.CustomFields.GeolocRegion,
|
||||||
GeolocCity = data.CustomFields.GeolocCity,
|
GeolocCountry = data.CustomFields.GeolocCountry,
|
||||||
GeolocRegion = data.CustomFields.GeolocRegion,
|
GeolocHasLocation = data.CustomFields.GeolocHasLocation,
|
||||||
GeolocCountry = data.CustomFields.GeolocCountry,
|
GeolocPostalCode = data.CustomFields.GeolocPostalCode
|
||||||
GeolocHasLocation = data.CustomFields.GeolocHasLocation,
|
});
|
||||||
GeolocPostalCode = data.CustomFields.GeolocPostalCode
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
if (!string.IsNullOrEmpty(jsonData.Next))
|
|
||||||
{
|
{
|
||||||
uri = new Uri(jsonData.Next);
|
logger.LogError(ex.ToString());
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(jsonData.Next))
|
||||||
|
{
|
||||||
|
uri = new Uri(jsonData.Next);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,6 +93,14 @@ namespace AS1024.GeoFeed.GeoFeedBuilder
|
||||||
return geoFeed;
|
return geoFeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual NetboxData? DeserializeJsonData(Stream stringResult)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<NetboxData>(stringResult, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected Uri BuildNetBoxURI(AddressFamily family)
|
protected Uri BuildNetBoxURI(AddressFamily family)
|
||||||
{
|
{
|
||||||
System.Collections.Specialized.NameValueCollection queryParameters = HttpUtility.ParseQueryString(string.Empty);
|
System.Collections.Specialized.NameValueCollection queryParameters = HttpUtility.ParseQueryString(string.Empty);
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
using AS1024.GeoFeed.Models;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a persistent cache GeoFeed provider
|
||||||
|
/// </summary>
|
||||||
|
public interface IGeoFeedPersistentCacheProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the provider
|
||||||
|
/// </summary>
|
||||||
|
public string ProviderName { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the GeoFeed
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>String of the CSV geofeed</returns>
|
||||||
|
public string GetGeoFeed();
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the GeoFeed in the cache backend
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pGeoFeeds">GeoFeed retrieved from the backend</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<bool> CacheGeoFeed(IList<IPGeoFeed> pGeoFeeds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using AS1024.GeoFeed.Models;
|
using AS1024.GeoFeed.Models;
|
||||||
|
|
||||||
namespace AS1024.GeoFeed.Interfaces
|
namespace AS1024.GeoFeed.Core.Interfaces
|
||||||
{
|
{
|
||||||
public interface IGeoFeedProvider
|
public interface IGeoFeedProvider
|
||||||
{
|
{
|
||||||
public string GeoFeedProviderName { get; }
|
public string GeoFeedProviderName { get; }
|
||||||
public Task<List<IPGeoFeed>> GetGeoFeedData();
|
public Task<List<IPGeoFeed>> GetGeoFeedDataAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
using AS1024.GeoFeed.Models;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.Core.Tools
|
||||||
|
{
|
||||||
|
public static class GeoFeedTools
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a CSV string for a given GeoFeed retrieved from various sources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="geoFeeds">GeoFeed returned from the source of truth.</param>
|
||||||
|
/// <param name="timeStamp">If a timestamp should be appended at the header.</param>
|
||||||
|
/// <param name="isCached">If the result is cached.</param>
|
||||||
|
/// <returns>CSV formatted string of GeoFeed data.</returns>
|
||||||
|
public static string ToGeoFeedCsv(this List<IPGeoFeed> geoFeeds, bool timeStamp = false, bool isCached = false)
|
||||||
|
{
|
||||||
|
if (geoFeeds == null) throw new ArgumentNullException(nameof(geoFeeds));
|
||||||
|
|
||||||
|
StringBuilder csvContent = new();
|
||||||
|
|
||||||
|
// Append timestamp header if required
|
||||||
|
if (timeStamp)
|
||||||
|
csvContent.AppendFormat("# GeoFeed generated on {0:R}\n", DateTime.UtcNow);
|
||||||
|
|
||||||
|
// Append cache status if required
|
||||||
|
if (isCached)
|
||||||
|
csvContent.AppendLine("# Geofeed data is returned from local in memory cache");
|
||||||
|
|
||||||
|
// Iterate over each GeoFeed entry to append its details to the CSV content
|
||||||
|
foreach (IPGeoFeed feed in geoFeeds)
|
||||||
|
{
|
||||||
|
// Using AppendFormat for a cleaner and more readable approach to constructing CSV lines
|
||||||
|
csvContent.AppendFormat("{0},{1},{2},{3},{4}\n", feed.Prefix, feed.GeolocCountry, feed.GeolocRegion, feed.GeolocCity, feed.GeolocPostalCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return csvContent.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
|
<PublishAot>true</PublishAot>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\AS1024.GeoFeed.Core.WebLogic\AS1024.GeoFeed.Core.WebLogic.csproj" />
|
||||||
|
<ProjectReference Include="..\AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using AS1024.GeoFeed.Models;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.MinimalAPI
|
||||||
|
{
|
||||||
|
[JsonSerializable(typeof(NetboxData))]
|
||||||
|
[JsonSerializable(typeof(Result))]
|
||||||
|
[JsonSerializable(typeof(CustomFields))]
|
||||||
|
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)]
|
||||||
|
internal partial class AppJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
#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/runtime:8.0-jammy-chiseled AS base
|
||||||
|
USER app
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build
|
||||||
|
# Install clang/zlib1g-dev dependencies for publishing to native
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
clang zlib1g-dev
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["AS1024.GeoFeed.MinimalAPI/AS1024.GeoFeed.MinimalAPI.csproj", "AS1024.GeoFeed.MinimalAPI/"]
|
||||||
|
RUN dotnet restore "./AS1024.GeoFeed.MinimalAPI/./AS1024.GeoFeed.MinimalAPI.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/AS1024.GeoFeed.MinimalAPI"
|
||||||
|
RUN dotnet build "./AS1024.GeoFeed.MinimalAPI.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "./AS1024.GeoFeed.MinimalAPI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=true /p:DebugType=None /p:DebugSymbols=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["./AS1024.GeoFeed.MinimalAPI"]
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
#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 alpine:latest AS base
|
||||||
|
ENV \
|
||||||
|
# UID of the non-root user 'app'
|
||||||
|
APP_UID=1654 \
|
||||||
|
# Configure web servers to bind to port 8080 when present
|
||||||
|
ASPNETCORE_HTTP_PORTS=8080 \
|
||||||
|
# Enable detection of running in a container
|
||||||
|
DOTNET_RUNNING_IN_CONTAINER=true \
|
||||||
|
# Set the invariant mode since ICU package isn't included (see https://github.com/dotnet/announcements/issues/20)
|
||||||
|
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true
|
||||||
|
|
||||||
|
#RUN apk add --upgrade --no-cache \
|
||||||
|
# ca-certificates-bundle \
|
||||||
|
# \
|
||||||
|
# # .NET dependencies
|
||||||
|
# libgcc \
|
||||||
|
# libssl3 \
|
||||||
|
# libstdc++ \
|
||||||
|
# zlib
|
||||||
|
|
||||||
|
# Create a non-root user and group
|
||||||
|
RUN addgroup \
|
||||||
|
--gid=$APP_UID \
|
||||||
|
app \
|
||||||
|
&& adduser \
|
||||||
|
--uid=$APP_UID \
|
||||||
|
--ingroup=app \
|
||||||
|
--disabled-password \
|
||||||
|
app
|
||||||
|
WORKDIR /app
|
||||||
|
USER app
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
|
||||||
|
# Install clang/zlib1g-dev dependencies for publishing to native
|
||||||
|
RUN apk add clang zlib-dev musl-dev libc6-compat
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["AS1024.GeoFeed.MinimalAPI/AS1024.GeoFeed.MinimalAPI.csproj", "AS1024.GeoFeed.MinimalAPI/"]
|
||||||
|
RUN dotnet restore "./AS1024.GeoFeed.MinimalAPI/./AS1024.GeoFeed.MinimalAPI.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/AS1024.GeoFeed.MinimalAPI"
|
||||||
|
RUN dotnet build "./AS1024.GeoFeed.MinimalAPI.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "./AS1024.GeoFeed.MinimalAPI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=true /p:DebugType=None /p:DebugSymbols=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["./AS1024.GeoFeed.MinimalAPI"]
|
||||||
|
|
@ -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/sdk:8.0-alpine AS build
|
||||||
|
# Install clang/zlib1g-dev dependencies for publishing to native
|
||||||
|
RUN apk add clang zlib-static zlib-dev musl-dev libc6-compat cmake autoconf make openssl-dev openssl-libs-static icu-static icu-dev
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["AS1024.GeoFeed.MinimalAPI/AS1024.GeoFeed.MinimalAPI.csproj", "AS1024.GeoFeed.MinimalAPI/"]
|
||||||
|
RUN dotnet restore "./AS1024.GeoFeed.MinimalAPI/./AS1024.GeoFeed.MinimalAPI.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/AS1024.GeoFeed.MinimalAPI"
|
||||||
|
RUN dotnet build "./AS1024.GeoFeed.MinimalAPI.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
RUN dotnet publish "./AS1024.GeoFeed.MinimalAPI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:StaticOpenSslLinking=true /p:StaticExecutable=true /p:StaticallyLinked=true /p:StripSymbols=true /p:DebugType=None /p:DebugSymbols=false
|
||||||
|
|
||||||
|
FROM scratch AS final
|
||||||
|
WORKDIR /tmp
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
COPY --from=build /etc/ssl/certs/* /etc/ssl/certs/
|
||||||
|
ENTRYPOINT ["./AS1024.GeoFeed.MinimalAPI"]
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
using AS1024.GeoFeed.Core.GeoFeedProviders;
|
||||||
|
using AS1024.GeoFeed.Core.Interfaces;
|
||||||
|
using AS1024.GeoFeed.Models;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.MinimalAPI
|
||||||
|
{
|
||||||
|
internal class NetboxAoTGeoFeedProvider : NetBoxGeoFeedProvider, IGeoFeedProvider
|
||||||
|
{
|
||||||
|
public NetboxAoTGeoFeedProvider(IConfiguration configuration,
|
||||||
|
ILogger<NetBoxGeoFeedProvider> logger,
|
||||||
|
IHttpClientFactory httpClientFactory)
|
||||||
|
: base(configuration, logger, httpClientFactory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override NetboxData? DeserializeJsonData(Stream stringResult)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize(stringResult, AppJsonSerializerContext.Default.NetboxData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
using AS1024.GeoFeed.Core.CacheService;
|
||||||
|
using AS1024.GeoFeed.Core.GeoFeedPreloader;
|
||||||
|
using AS1024.GeoFeed.Core.Interfaces;
|
||||||
|
using AS1024.GeoFeed.Core.WebLogic;
|
||||||
|
|
||||||
|
namespace AS1024.GeoFeed.MinimalAPI
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateSlimBuilder(args);
|
||||||
|
builder.Services.AddTransient<IGeoFeedProvider, NetboxAoTGeoFeedProvider>();
|
||||||
|
builder.Services.AddHostedService<GeoFeedCacheService>();
|
||||||
|
builder.Services.AddHostedService<PreLoadGeoFeed>();
|
||||||
|
builder.Services.AddTransient<IGeoFeedPersistentCacheProvider, GeoFeedLocalFileCache>();
|
||||||
|
builder.Services.AddScoped<GeoFeedReturn>();
|
||||||
|
builder.Services.AddMemoryCache();
|
||||||
|
builder.Services.AddLogging();
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
builder.Services.ConfigureHttpJsonOptions(options =>
|
||||||
|
{
|
||||||
|
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
|
||||||
|
});
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.Map("/geofeed.csv", async (GeoFeedReturn feedReturn) =>
|
||||||
|
{
|
||||||
|
return await feedReturn.GetGeoFeed();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.Map("/geofeed", async (GeoFeedReturn feedReturn) =>
|
||||||
|
{
|
||||||
|
return await feedReturn.GetGeoFeed();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "geofeed.csv",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5127"
|
||||||
|
},
|
||||||
|
"Docker": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/todos",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||||
|
},
|
||||||
|
"publishAllPorts": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
using System.Text.Json.Serialization;
|
namespace AS1024.GeoFeed.Models
|
||||||
namespace AS1024.GeoFeed.Models
|
|
||||||
{
|
{
|
||||||
public class NetboxData
|
public class NetboxData
|
||||||
{
|
{
|
||||||
public List<Result>? Results { get; set; }
|
public List<Result>? Results { get; set; }
|
||||||
public string? Next { get; set; }
|
public string? Next { get; set; }
|
||||||
public string? Previous { get; set; }
|
public string? Previous { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Result
|
public class Result
|
||||||
|
|
@ -41,7 +40,8 @@ namespace AS1024.GeoFeed.Models
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class represents the IP GeoFeed Entry
|
/// This class represents the IP GeoFeed Entry
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IPGeoFeed : CustomFields {
|
public class IPGeoFeed : CustomFields
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the IP Prefix for the associated GeoFeed entry
|
/// Represents the IP Prefix for the associated GeoFeed entry
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -3,7 +3,20 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.8.34330.188
|
VisualStudioVersion = 17.8.34330.188
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AS1024.GeoFeed", "AS1024.GeoFeed\AS1024.GeoFeed.csproj", "{6292097C-7F35-45BB-B2B0-1918DF49FE7D}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed", "AS1024.GeoFeed\AS1024.GeoFeed.csproj", "{6292097C-7F35-45BB-B2B0-1918DF49FE7D}"
|
||||||
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
|
{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5} = {8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.Models", "AS1024.GeoFeed.Models\AS1024.GeoFeed.Models.csproj", "{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.Core", "AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj", "{EE6D6C87-29C4-42A1-8251-7D2BF20F1797}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.MinimalAPI", "AS1024.GeoFeed.MinimalAPI\AS1024.GeoFeed.MinimalAPI.csproj", "{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.Core.SqliteGeoFeedCache", "AS1024.GeoFeed.Core.SqliteGeoFeedCache\AS1024.GeoFeed.Core.SqliteGeoFeedCache.csproj", "{3459BB31-FA7A-44D1-872D-C5338ACFBF80}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AS1024.GeoFeed.Core.WebLogic", "AS1024.GeoFeed.Core.WebLogic\AS1024.GeoFeed.Core.WebLogic.csproj", "{58BDCE89-FCC0-478F-BBDE-B89833712AAB}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|
@ -15,6 +28,26 @@ Global
|
||||||
{6292097C-7F35-45BB-B2B0-1918DF49FE7D}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{6292097C-7F35-45BB-B2B0-1918DF49FE7D}.Release|Any CPU.Build.0 = Release|Any CPU
|
{6292097C-7F35-45BB-B2B0-1918DF49FE7D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8BA82A53-29E7-44A2-91BA-57C15BB4B0F5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{EE6D6C87-29C4-42A1-8251-7D2BF20F1797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{EE6D6C87-29C4-42A1-8251-7D2BF20F1797}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{EE6D6C87-29C4-42A1-8251-7D2BF20F1797}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{EE6D6C87-29C4-42A1-8251-7D2BF20F1797}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{58BDCE89-FCC0-478F-BBDE-B89833712AAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{58BDCE89-FCC0-478F-BBDE-B89833712AAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{58BDCE89-FCC0-478F-BBDE-B89833712AAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{58BDCE89-FCC0-478F-BBDE-B89833712AAB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,23 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\AS1024.GeoFeed.Core.SqliteGeoFeedCache\AS1024.GeoFeed.Core.SqliteGeoFeedCache.csproj" />
|
||||||
|
<ProjectReference Include="..\AS1024.GeoFeed.Core.WebLogic\AS1024.GeoFeed.Core.WebLogic.csproj" />
|
||||||
|
<ProjectReference Include="..\AS1024.GeoFeed.Core\AS1024.GeoFeed.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\AS1024.GeoFeed.Models\AS1024.GeoFeed.Models.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using AS1024.GeoFeed.Core.WebLogic;
|
||||||
using AS1024.GeoFeed.Interfaces;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using AS1024.GeoFeed.GeoFeedBuilder;
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using AS1024.GeoFeed.Models;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace AS1024.GeoFeed.Controllers
|
namespace AS1024.GeoFeed.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -13,52 +9,18 @@ namespace AS1024.GeoFeed.Controllers
|
||||||
|
|
||||||
public class GeofeedController : ControllerBase
|
public class GeofeedController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IGeoFeedProvider builder;
|
private readonly GeoFeedReturn feedReturn;
|
||||||
private readonly IMemoryCache memoryCache;
|
|
||||||
private readonly IWebHostEnvironment environment;
|
|
||||||
private readonly ILogger<GeofeedController> logger;
|
|
||||||
private const string GeoFeedCacheKey = "GeoFeedData";
|
|
||||||
|
|
||||||
public GeofeedController(IGeoFeedProvider builder,
|
public GeofeedController(GeoFeedReturn feedReturn)
|
||||||
IMemoryCache memoryCache,
|
{
|
||||||
IWebHostEnvironment environment,
|
this.feedReturn = feedReturn;
|
||||||
ILogger<GeofeedController> logger) {
|
|
||||||
this.logger = logger;
|
|
||||||
this.builder = builder;
|
|
||||||
this.memoryCache = memoryCache;
|
|
||||||
this.environment = environment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("")]
|
[Route("")]
|
||||||
public async Task<IActionResult> Get()
|
public async Task<IResult> Get()
|
||||||
{
|
{
|
||||||
try
|
return await feedReturn.GetGeoFeed();
|
||||||
{
|
|
||||||
if (!memoryCache.TryGetValue(GeoFeedCacheKey, out List<IPGeoFeed>? feed))
|
|
||||||
{
|
|
||||||
feed = await builder.GetGeoFeedData();
|
|
||||||
if (environment.IsProduction())
|
|
||||||
{
|
|
||||||
MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions()
|
|
||||||
.SetSlidingExpiration(TimeSpan.FromMinutes(15));
|
|
||||||
memoryCache.Set(GeoFeedCacheKey, feed, cacheEntryOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string csvContent = feed.ToGeoFeedCsv(); // Assuming ToGeoFeedCsv() returns a string in CSV format.
|
|
||||||
byte[] contentBytes = Encoding.UTF8.GetBytes(csvContent);
|
|
||||||
string contentType = "text/csv";
|
|
||||||
|
|
||||||
return new FileContentResult(contentBytes, contentType)
|
|
||||||
{
|
|
||||||
FileDownloadName = "geofeed.csv"
|
|
||||||
};
|
|
||||||
} catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.LogError($"Geofeed generation failed. Exception: {ex}");
|
|
||||||
return StatusCode(500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
#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 alpine:latest AS base
|
||||||
|
ENV \
|
||||||
|
# UID of the non-root user 'app'
|
||||||
|
APP_UID=1654 \
|
||||||
|
# Configure web servers to bind to port 8080 when present
|
||||||
|
ASPNETCORE_HTTP_PORTS=8080 \
|
||||||
|
# Enable detection of running in a container
|
||||||
|
DOTNET_RUNNING_IN_CONTAINER=true \
|
||||||
|
# Set the invariant mode since ICU package isn't included (see https://github.com/dotnet/announcements/issues/20)
|
||||||
|
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true
|
||||||
|
|
||||||
|
RUN apk add --upgrade --no-cache \
|
||||||
|
ca-certificates-bundle \
|
||||||
|
\
|
||||||
|
# .NET dependencies
|
||||||
|
libgcc \
|
||||||
|
libssl3 \
|
||||||
|
libstdc++ \
|
||||||
|
zlib
|
||||||
|
|
||||||
|
# Create a non-root user and group
|
||||||
|
RUN addgroup \
|
||||||
|
--gid=$APP_UID \
|
||||||
|
app \
|
||||||
|
&& adduser \
|
||||||
|
--uid=$APP_UID \
|
||||||
|
--ingroup=app \
|
||||||
|
--disabled-password \
|
||||||
|
app
|
||||||
|
WORKDIR /app
|
||||||
|
USER app
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
ARG TARGETARCH
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["AS1024.GeoFeed/AS1024.GeoFeed.csproj", "AS1024.GeoFeed/"]
|
||||||
|
RUN dotnet restore "./AS1024.GeoFeed/./AS1024.GeoFeed.csproj" -a $TARGETARCH
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/AS1024.GeoFeed"
|
||||||
|
RUN dotnet build "./AS1024.GeoFeed.csproj" -c $BUILD_CONFIGURATION -o /app/build -a $TARGETARCH /p:StaticLink=true
|
||||||
|
|
||||||
|
FROM --platform=$BUILDPLATFORM build AS publish
|
||||||
|
ARG BUILD_CONFIGURATION=Release
|
||||||
|
ARG TARGETARCH
|
||||||
|
RUN dotnet publish "./AS1024.GeoFeed.csproj" -c $BUILD_CONFIGURATION -o /app/publish -a $TARGETARCH --self-contained
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["/app/AS1024.GeoFeed"]
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
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 (IPGeoFeed feed in geoFeeds)
|
|
||||||
{
|
|
||||||
csvContent.AppendLine($"{feed.Prefix},{feed.GeolocCountry},{feed.GeolocRegion},{feed.GeolocCity},{feed.GeolocPostalCode}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return csvContent.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
using AS1024.GeoFeed.GeoFeedBuilder;
|
using AS1024.GeoFeed.Core.CacheService;
|
||||||
using AS1024.GeoFeed.Interfaces;
|
using AS1024.GeoFeed.Core.GeoFeedPreloader;
|
||||||
|
using AS1024.GeoFeed.Core.GeoFeedProviders;
|
||||||
|
using AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache;
|
||||||
|
using AS1024.GeoFeed.Core.Interfaces;
|
||||||
|
using AS1024.GeoFeed.Core.WebLogic;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace AS1024.GeoFeed
|
namespace AS1024.GeoFeed
|
||||||
{
|
{
|
||||||
|
|
@ -11,6 +16,14 @@ namespace AS1024.GeoFeed
|
||||||
|
|
||||||
builder.Services.AddHostedService<PreLoadGeoFeed>();
|
builder.Services.AddHostedService<PreLoadGeoFeed>();
|
||||||
builder.Services.AddTransient<IGeoFeedProvider, NetBoxGeoFeedProvider>();
|
builder.Services.AddTransient<IGeoFeedProvider, NetBoxGeoFeedProvider>();
|
||||||
|
builder.Services.AddScoped<GeoFeedReturn>();
|
||||||
|
builder.Services.AddDbContext<GeoFeedCacheDbContext>(
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
options.UseSqlite(builder.Configuration.GetConnectionString("LocalFeedCache"));
|
||||||
|
});
|
||||||
|
builder.Services.AddScoped<IGeoFeedPersistentCacheProvider, GeoFeedSqliteCache>();
|
||||||
|
builder.Services.AddHostedService<GeoFeedCacheService>();
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
builder.Services.AddMemoryCache();
|
builder.Services.AddMemoryCache();
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"LocalFeedCache": "Data Source=localcache.db"
|
||||||
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"APIKey": "",
|
"APIKey": "",
|
||||||
"NetBoxHost": ""
|
"NetBoxHost": ""
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
BSD 3-Clause License
|
BSD 3-Clause License
|
||||||
|
|
||||||
Copyright (c) [year], [fullname]
|
Copyright (c) 2024, 12393239 Canada Inc.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are met:
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
|
||||||
78
README.md
78
README.md
|
|
@ -4,23 +4,58 @@
|
||||||
|
|
||||||
This web application provides a self-published IP geolocation feed, conforming to the standards set out in [RFC 8805](https://www.rfc-editor.org/rfc/rfc8805). It is designed to interface with NetBox, a web-based infrastructure resource modeling (IRM) tool, to retrieve and format geolocation data for IP addresses.
|
This web application provides a self-published IP geolocation feed, conforming to the standards set out in [RFC 8805](https://www.rfc-editor.org/rfc/rfc8805). It is designed to interface with NetBox, a web-based infrastructure resource modeling (IRM) tool, to retrieve and format geolocation data for IP addresses.
|
||||||
|
|
||||||
The application is implemented in C# using .NET 8.0, ensuring a robust and modern back-end architecture. It's built to communicate securely over HTTPS with the NetBox API, adhering to best practices for data transmission and security.
|
The application is implemented in C# using .NET 8.0, ensuring a robust and modern back-end architecture. It's built to communicate securely over HTTPS with the NetBox API, adhering to best practices for data transmission and security.
|
||||||
|
|
||||||
|
This project was inspired by the GitHub project [GeoBox](https://github.com/FrumentumNL/GeoBox), which provided foundational ideas for the development of this application.
|
||||||
|
|
||||||
|
## Application Variants
|
||||||
|
|
||||||
|
### AS1024.GeoFeed - MVC Version (Recommended)
|
||||||
|
|
||||||
|
The standard version of this application, named `AS1024.GeoFeed`, is built using the Model-View-Controller (MVC) architecture. This variant is fully supported and recommended for most use cases. It offers a complete set of features and is optimized for robustness and scalability.
|
||||||
|
|
||||||
|
### AS1024.GeoFeed.MinimalAPI - MinimalAPI Version (Experimental)
|
||||||
|
|
||||||
|
In addition to the standard MVC version, there is an experimental MinimalAPI version of the application named `AS1024.GeoFeed.MinimalAPI`. This variant is designed for environments that require extremely fast startup times, such as serverless containers. However, it comes with limited support and a reduced feature set. We recommend using the `AS1024.GeoFeed.MinimalAPI` version only if your deployment environment necessitates near-instant startup times and you are comfortable with its experimental nature and limitations.
|
||||||
|
|
||||||
|
**Note**: While the `AS1024.GeoFeed.MinimalAPI` version offers performance benefits in specific scenarios, we strongly recommend deploying the `AS1024.GeoFeed` MVC version for most applications to ensure full functionality and support.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **GeoFeed Generation**: Dynamically generates a geolocation feed in CSV format as specified in RFC 8805.
|
- **GeoFeed Generation**: Dynamically generates a geolocation feed in CSV format as specified in RFC 8805.
|
||||||
- **Caching Mechanism**: Implements an efficient caching strategy to reduce redundant API calls and enhance performance.
|
- **Caching Mechanism**: Implements an efficient caching strategy to reduce redundant API calls and enhance performance.
|
||||||
|
- **Local Disk Fallback Caching Mechanism**: In the event of a failure to communicate with the NetBox instance specified, the web app will return data that is locally cached inside a SQLite database. In the minimal API version of the GeoFeed application, this will be stored as a file, rather than a SQLite database as Minimal API does not yet support Entity Framework Core at this time.
|
||||||
- **Secure Communication**: Ensures secure data retrieval from NetBox over HTTPS.
|
- **Secure Communication**: Ensures secure data retrieval from NetBox over HTTPS.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The application requires the following configuration variables to be set:
|
The application requires the following configuration variables to be set, depending on the version you are using:
|
||||||
|
|
||||||
|
### For AS1024.GeoFeed - MVC Version
|
||||||
1. **APIKey**: This is the API key used for authenticating with the NetBox API. Ensure this key has the necessary permissions to access the required resources.
|
1. **APIKey**: This is the API key used for authenticating with the NetBox API. Ensure this key has the necessary permissions to access the required resources.
|
||||||
2. **NetBoxHost**: The hostname of the NetBox instance from which the application retrieves data. For example, `netbox.example.com`.
|
2. **NetBoxHost**: The hostname of the NetBox instance from which the application retrieves data. For example, `netbox.example.com`.
|
||||||
|
3. **LocalFeedCache**: This connection string is for a local SQLite Database (using EF Core) that caches the geofeed data from Netbox. The syntax should follow the standard SQLite EF Core connection string format.
|
||||||
|
|
||||||
|
### For AS1024.GeoFeed.MinimalAPI - MinimalAPI Version
|
||||||
|
1. **APIKey**: This is the API key used for authenticating with the NetBox API. Ensure this key has the necessary permissions to access the required resources.
|
||||||
|
2. **NetBoxHost**: The hostname of the NetBox instance from which the application retrieves data. For example, `netbox.example.com`.
|
||||||
|
3. **TempCache** (optional): This configuration is specific to the Minimal API version. It is optional, but if users wish to specify a different location for storing and serving the cached geofeed data, this value can be adjusted to point to the desired save location.
|
||||||
|
|
||||||
These variables can be set in your application's configuration file or through environment variables, depending on your deployment strategy.
|
These variables can be set in your application's configuration file or through environment variables, depending on your deployment strategy.
|
||||||
|
|
||||||
|
|
||||||
|
## NetBox Custom Fields
|
||||||
|
|
||||||
|
Ensure that your NetBox instance is configured with the following custom fields:
|
||||||
|
|
||||||
|
- `geoloc_city`: (Text) Represents the city and is not required to be filled in.
|
||||||
|
- `geoloc_country`: (Selection) Represents the country and is not required to be filled in.
|
||||||
|
- `geoloc_has_location`: (Boolean) Indicates if there is geolocation data available and is required.
|
||||||
|
- `geoloc_postal_code`: (Text) Represents the postal code and is not required to be filled in.
|
||||||
|
- `geoloc_region`: (Selection) Represents the region and is not required to be filled in.
|
||||||
|
|
||||||
|
These fields are critical for the application to accurately retrieve and format geolocation data.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
To build and run the application, follow these steps:
|
To build and run the application, follow these steps:
|
||||||
|
|
@ -32,9 +67,36 @@ To build and run the application, follow these steps:
|
||||||
5. After a successful build, you can start the application by running `dotnet run`.
|
5. After a successful build, you can start the application by running `dotnet run`.
|
||||||
6. The application will start, and you can access the endpoints as specified.
|
6. The application will start, and you can access the endpoints as specified.
|
||||||
|
|
||||||
## Docker Build
|
## Building a Docker Image
|
||||||
|
|
||||||
A Dockerfile is provided for your convienence. This has not been tested as of yet.
|
### Building a Docker Image for the Minimal API Version
|
||||||
|
|
||||||
|
To containerize the `AS1024.GeoFeed` version using Docker, you can follow these steps to build a Docker image:
|
||||||
|
|
||||||
|
1. Open your terminal or command line interface.
|
||||||
|
2. Navigate to the root directory of this repository.
|
||||||
|
3. Execute the following Docker build command: ``docker buildx build --platform=linux/amd64,linux/arm64 -f .\AS1024.GeoFeed\Dockerfile .``
|
||||||
|
|
||||||
|
### Building a Docker Image for the Minimal API Version
|
||||||
|
|
||||||
|
To containerize the `AS1024.GeoFeed.MinimalAPI` version using Docker, you can follow these steps to build a Docker image:
|
||||||
|
|
||||||
|
1. Open your terminal or command line interface.
|
||||||
|
2. Navigate to the root directory of this repository.
|
||||||
|
3. Execute the following Docker build command: ``docker buildx build --platform=linux/amd64 -f .\AS1024.GeoFeed.MinimalAPI\Dockerfile.alpine-selfcontained .``
|
||||||
|
|
||||||
|
**Currently the minimal API version does not support being cross compiled for different CPU architectures. It is best built with the same CPU architecture where the build machine is targeting to.**
|
||||||
|
|
||||||
|
Ensure you have Docker installed and configured on your machine before executing this command. This Docker image can then be used to deploy the application in containerized environments such as Kubernetes or Docker Compose setups.
|
||||||
|
|
||||||
|
## Docker Deployment
|
||||||
|
|
||||||
|
To deploy the Docker container of this GeoFeed application, simply pull the image for the following variants:
|
||||||
|
|
||||||
|
- MVC Variant ``docker pull git.startmywifi.com/as1024/geofeed:latest``
|
||||||
|
- Minimal API Variant ``docker pull git.startmywifi.com/as1024/geofeed:aot-minimal``
|
||||||
|
|
||||||
|
To configure the container, please refer to the Configuration section above. You should be able to configure this with either an environment variable or map an ``appSettings.json`` file to the container.
|
||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
|
|
@ -45,8 +107,10 @@ The application provides the following key endpoints:
|
||||||
|
|
||||||
## Security and Compliance
|
## Security and Compliance
|
||||||
|
|
||||||
This application is designed to always communicate over HTTPS with NetBox, ensuring that the data transfer is encrypted and secure.
|
This application is designed to always communicate over HTTPS with NetBox, ensuring that the data transfer is encrypted and secure.
|
||||||
|
|
||||||
---
|
## Extending Beyond NetBox
|
||||||
|
|
||||||
For more information about configuring and using this application, please refer to the official .NET documentation and the NetBox API guide.
|
If your current IPAM solution is not netbox and wish to extend this web application to use the desired IPAM solution of choice, the interface `IGeoFeedProvider` is available for extensibility. To use your custom IPAM backend ensure that `NetboxAoTGeoFeedProvider` and `NetboxGeoFeedProvider` are not registered in the dependency injection container in the Web Apps. Once unregistered, register your custom IPAM communication backend provider to your needs and the web app should work in both AOT and MVC mode.
|
||||||
|
|
||||||
|
Currently the Minimal API implementation of this web application only supports code that does not require reflection. This is a known limitation of native AOT deployments. If your code utilizes reflection or is not properly adapted for source generation, the minimal API version will **not work**.
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{$GEOFEEDDOMAIN}:443 {
|
||||||
|
log {
|
||||||
|
level INFO
|
||||||
|
output file {$LOG_FILE} {
|
||||||
|
roll_size 10MB
|
||||||
|
roll_keep 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use the ACME HTTP-01 challenge to get a cert for the configured domain.
|
||||||
|
tls {$EMAIL}
|
||||||
|
|
||||||
|
reverse_proxy geofeed:8080
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
version: '3.7'
|
||||||
|
services:
|
||||||
|
geofeed:
|
||||||
|
# use the image tag aot-minimal for the AOT version for fastest startup performance
|
||||||
|
image: git.startmywifi.com/as1024/geofeed:latest
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- './data:/data'
|
||||||
|
environment:
|
||||||
|
- ASPNETCORE_URLS=http://+:8080
|
||||||
|
- ConnectionString__LocalFeedCache=Data Source=/data/geofeed-cache.db
|
||||||
|
- APIKey=APIKeyHere
|
||||||
|
- NetBoxHost=netboxhosthere
|
||||||
|
caddy:
|
||||||
|
image: caddy:2
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 443:443
|
||||||
|
- 443:443/udp
|
||||||
|
volumes:
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- ./caddy-config:/config
|
||||||
|
- ./caddy-data:/data
|
||||||
|
environment:
|
||||||
|
GEOFEEDDOMAIN: "https://geofeed.exampleas.net"
|
||||||
|
EMAIL: "noc@example.com" # The email address to use for ACME registration.
|
||||||
|
LOG_FILE: "/data/access.log"
|
||||||
Loading…
Reference in New Issue