Skip to content

Commit 1f90969

Browse files
macsuxTim Hess
authored andcommitted
Add support for DbMigrations endpoint (#87)
* Add reflection-based db migrations endpoint with initial support for EF Core
1 parent 564ff47 commit 1f90969

19 files changed

Lines changed: 848 additions & 1 deletion

.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
# Common IntelliJ Platform excludes
2+
3+
# User specific
4+
**/.idea/**/workspace.xml
5+
**/.idea/**/tasks.xml
6+
**/.idea/shelf/*
7+
**/.idea/dictionaries
8+
9+
**/.idea/indexLayout.xml
10+
**/.idea/vcs.xml
11+
12+
# Sensitive or high-churn files
13+
**/.idea/**/dataSources/
14+
**/.idea/**/dataSources.ids
15+
**/.idea/**/dataSources.xml
16+
**/.idea/**/dataSources.local.xml
17+
**/.idea/**/sqlDataSources.xml
18+
**/.idea/**/dynamic.xml
19+
20+
# Rider
21+
# Rider auto-generates .iml files, and contentModel.xml
22+
**/.idea/**/*.iml
23+
**/.idea/**/contentModel.xml
24+
**/.idea/**/modules.xml
25+
26+
127
## Ignore Visual Studio temporary files, build results, and
228
## files generated by popular Visual Studio add-ons.
329

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2017 the original author or authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.Collections.Generic;
16+
17+
namespace Steeltoe.Management.EndpointBase.DbMigrations
18+
{
19+
public class DbMigrationsDescriptor
20+
{
21+
public List<string> PendingMigrations { get; set; } = new List<string>();
22+
23+
public List<string> AppliedMigrations { get; set; } = new List<string>();
24+
}
25+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright 2017 the original author or authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Microsoft.Extensions.Logging;
16+
using Steeltoe.Management.Endpoint;
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Data.Common;
20+
using System.Linq;
21+
using System.Reflection;
22+
23+
namespace Steeltoe.Management.EndpointBase.DbMigrations
24+
{
25+
public class DbMigrationsEndpoint : AbstractEndpoint<Dictionary<string, DbMigrationsDescriptor>>
26+
{
27+
/// <summary>
28+
/// Hacky class to allow mocking migration methods in unit tests
29+
/// </summary>
30+
public class DbMigrationsEndpointHelper
31+
{
32+
internal virtual Assembly ScanRootAssembly => Assembly.GetEntryAssembly();
33+
34+
internal virtual IEnumerable<string> GetPendingMigrations(object context) => GetMigrationsReflectively(context, _getPendingMigrations);
35+
36+
internal virtual IEnumerable<string> GetAppliedMigrations(object context) => GetMigrationsReflectively(context, _getAppliedMigrations);
37+
38+
internal virtual IEnumerable<string> GetMigrations(object context) => GetMigrationsReflectively(context, _getMigrations);
39+
40+
private IEnumerable<string> GetMigrationsReflectively(object dbContext, MethodInfo method)
41+
{
42+
var dbFacade = _getDatabase.Invoke(dbContext, null);
43+
return (IEnumerable<string>)method.Invoke(null, new[] { dbFacade });
44+
}
45+
}
46+
47+
internal static Type _dbContextType;
48+
internal static MethodInfo _getDatabase;
49+
internal static MethodInfo _getPendingMigrations;
50+
internal static MethodInfo _getAppliedMigrations;
51+
internal static MethodInfo _getMigrations;
52+
53+
private readonly IServiceProvider _container;
54+
private readonly DbMigrationsEndpointHelper _endpointHelper;
55+
private readonly ILogger<DbMigrationsEndpoint> _logger;
56+
57+
static DbMigrationsEndpoint()
58+
{
59+
_dbContextType = Type.GetType("Microsoft.EntityFrameworkCore.DbContext, Microsoft.EntityFrameworkCore");
60+
_getDatabase = _dbContextType.GetProperty("Database", BindingFlags.Public | BindingFlags.Instance).GetMethod;
61+
var extensions = Type.GetType("Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions,Microsoft.EntityFrameworkCore.Relational");
62+
_getPendingMigrations = extensions.GetMethod("GetPendingMigrations", BindingFlags.Static | BindingFlags.Public);
63+
_getAppliedMigrations = extensions.GetMethod("GetAppliedMigrations", BindingFlags.Static | BindingFlags.Public);
64+
_getMigrations = extensions.GetMethod("GetMigrations", BindingFlags.Static | BindingFlags.Public);
65+
}
66+
67+
public DbMigrationsEndpoint(
68+
IDbMigrationsOptions options,
69+
IServiceProvider container,
70+
ILogger<DbMigrationsEndpoint> logger = null)
71+
: this(options, container, new DbMigrationsEndpointHelper(), logger)
72+
{
73+
}
74+
75+
public DbMigrationsEndpoint(
76+
IDbMigrationsOptions options,
77+
IServiceProvider container,
78+
DbMigrationsEndpointHelper endpointHelper,
79+
ILogger<DbMigrationsEndpoint> logger = null)
80+
: base(options)
81+
{
82+
_container = container;
83+
_endpointHelper = endpointHelper;
84+
_logger = logger;
85+
}
86+
87+
public override Dictionary<string, DbMigrationsDescriptor> Invoke() => DoInvoke();
88+
89+
private Dictionary<string, DbMigrationsDescriptor> DoInvoke()
90+
{
91+
var result = new Dictionary<string, DbMigrationsDescriptor>();
92+
var knownEfContexts = _endpointHelper.ScanRootAssembly
93+
.GetReferencedAssemblies()
94+
.Select(Assembly.Load)
95+
.SelectMany(x => x.DefinedTypes)
96+
.Union(_endpointHelper.ScanRootAssembly.DefinedTypes)
97+
.Where(type => !type.IsAbstract && type.AsType() != _dbContextType && _dbContextType.GetTypeInfo().IsAssignableFrom(type.AsType()))
98+
.Select(typeInfo => typeInfo.AsType())
99+
.ToList();
100+
foreach (var contextType in knownEfContexts)
101+
{
102+
var dbContext = _container.GetService(contextType);
103+
if (dbContext == null)
104+
{
105+
continue;
106+
}
107+
108+
var descriptor = new DbMigrationsDescriptor();
109+
var contextName = dbContext.GetType().Name;
110+
result.Add(contextName, descriptor);
111+
try
112+
{
113+
descriptor.PendingMigrations = _endpointHelper.GetPendingMigrations(dbContext).ToList();
114+
descriptor.AppliedMigrations = _endpointHelper.GetAppliedMigrations(dbContext).ToList();
115+
}
116+
catch (DbException e) when (e.Message.Contains("exist"))
117+
{
118+
// todo: maybe improve detection logic when database is new. hard to do generically across all providers
119+
descriptor.PendingMigrations = _endpointHelper.GetMigrations(dbContext).ToList();
120+
}
121+
}
122+
123+
return result;
124+
}
125+
}
126+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2017 the original author or authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Microsoft.Extensions.Configuration;
16+
using Steeltoe.Management.Endpoint;
17+
using Steeltoe.Management.Endpoint.Security;
18+
using System;
19+
20+
namespace Steeltoe.Management.EndpointBase.DbMigrations
21+
{
22+
public class DbMigrationsEndpointOptions : AbstractEndpointOptions, IDbMigrationsOptions
23+
{
24+
private const string MANAGEMENT_INFO_PREFIX = "management:endpoints:dbmigrations";
25+
26+
public DbMigrationsEndpointOptions()
27+
{
28+
Id = "dbmigrations";
29+
RequiredPermissions = Permissions.RESTRICTED;
30+
}
31+
32+
public DbMigrationsEndpointOptions(IConfiguration config)
33+
: base(MANAGEMENT_INFO_PREFIX, config)
34+
{
35+
if (string.IsNullOrEmpty(Id))
36+
{
37+
Id = "dbmigrations";
38+
}
39+
40+
if (RequiredPermissions == Permissions.UNDEFINED)
41+
{
42+
RequiredPermissions = Permissions.RESTRICTED;
43+
}
44+
}
45+
46+
public string[] KeysToSanitize => Array.Empty<string>();
47+
}
48+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2017 the original author or authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Steeltoe.Management.Endpoint;
16+
17+
namespace Steeltoe.Management.EndpointBase.DbMigrations
18+
{
19+
public interface IDbMigrationsOptions : IEndpointOptions
20+
{
21+
}
22+
}

src/Management/src/EndpointBase/IEndpointOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
using Steeltoe.Management.Endpoint.Security;
1616
using System;
17+
using System.Collections.Generic;
1718

1819
namespace Steeltoe.Management.Endpoint
1920
{

src/Management/src/EndpointBase/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@
3434
[assembly: InternalsVisibleTo("Steeltoe.Management.EndpointOwin.Test")]
3535
[assembly: InternalsVisibleTo("Steeltoe.Management.EndpointWeb.Test")]
3636
[assembly: InternalsVisibleTo("Steeltoe.Management.EndpointAutofac.Test")]
37+
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2017 the original author or authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Microsoft.AspNetCore.Http;
16+
using Microsoft.Extensions.Logging;
17+
using Steeltoe.Management.Endpoint.Middleware;
18+
using Steeltoe.Management.EndpointBase.DbMigrations;
19+
using System.Collections.Generic;
20+
using System.Threading.Tasks;
21+
22+
namespace Steeltoe.Management.Endpoint.DbMigrations
23+
{
24+
public class DbMigrationsEndpointMiddleware : EndpointMiddleware<Dictionary<string, DbMigrationsDescriptor>>
25+
{
26+
private RequestDelegate _next;
27+
28+
public DbMigrationsEndpointMiddleware(RequestDelegate next, DbMigrationsEndpoint endpoint, IEnumerable<IManagementOptions> mgmtOptions, ILogger<DbMigrationsEndpointMiddleware> logger = null)
29+
: base(endpoint, mgmtOptions, logger: logger)
30+
{
31+
_next = next;
32+
}
33+
34+
public async Task Invoke(HttpContext context)
35+
{
36+
if (RequestVerbAndPathMatch(context.Request.Method, context.Request.Path.Value))
37+
{
38+
await HandleEntityFrameworkRequestAsync(context);
39+
}
40+
else
41+
{
42+
await _next(context);
43+
}
44+
}
45+
46+
protected internal async Task HandleEntityFrameworkRequestAsync(HttpContext context)
47+
{
48+
var serialInfo = HandleRequest();
49+
_logger?.LogDebug("Returning: {0}", serialInfo);
50+
context.Response.Headers.Add("Content-Type", "application/vnd.spring-boot.actuator.v2+json");
51+
await context.Response.WriteAsync(serialInfo);
52+
}
53+
}
54+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2017 the original author or authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Microsoft.AspNetCore.Builder;
16+
using System;
17+
18+
namespace Steeltoe.Management.Endpoint.DbMigrations
19+
{
20+
public static class EndpointApplicationBuilderExtensions
21+
{
22+
/// <summary>
23+
/// Enable the EntityFramework middleware
24+
/// </summary>
25+
/// <param name="builder">Your application builder</param>
26+
public static void UseDbMigrationsActuator(this IApplicationBuilder builder)
27+
{
28+
if (builder == null)
29+
{
30+
throw new ArgumentNullException(nameof(builder));
31+
}
32+
33+
builder.UseMiddleware<DbMigrationsEndpointMiddleware>();
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)