-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(scan): implement CI discovery provider
closes #88
- Loading branch information
Showing
12 changed files
with
791 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,5 @@ public interface CiDiscovery | |
{ | ||
CiServer? Server { get; } | ||
bool IsCi { get; } | ||
bool IsPr { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,48 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Text.Json; | ||
|
||
namespace SecTester.Scan.CI; | ||
|
||
// TODO ([email protected]): rework using 'ci-info' compatible implementation | ||
internal class DefaultCiDiscovery : CiDiscovery | ||
{ | ||
public CiServer? Server { get; } | ||
|
||
public bool IsCi => Server != null; | ||
|
||
public bool IsPr { get; } | ||
|
||
public DefaultCiDiscovery(IDictionary? env = default) | ||
{ | ||
env ??= Environment.GetEnvironmentVariables(); | ||
|
||
var vendors = JsonSerializer.Deserialize<Vendor[]>( | ||
ResourceUtils.GetEmbeddedResourceContent<DefaultCiDiscovery>("SecTester.Scan.CI.vendors.json"), | ||
new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); | ||
|
||
if (vendors is null) | ||
{ | ||
return; | ||
} | ||
|
||
var matcher = new VendorMatcher(env); | ||
|
||
var vendor = vendors.FirstOrDefault(x => matcher.MatchEnvElement(x.Env)); | ||
|
||
if (vendor is null) | ||
{ | ||
return; | ||
} | ||
|
||
Server = typeof(CiServer) | ||
.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) | ||
.Where(x => x.Name.Equals(vendor.Constant, StringComparison.OrdinalIgnoreCase)) | ||
.Select(x => x.GetValue(null)) | ||
.Cast<CiServer>() | ||
.FirstOrDefault() ?? new CiServer(vendor.Name); | ||
|
||
IsPr = matcher.MatchPrElement(vendor.Pr); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using System; | ||
using System.IO; | ||
using System.Reflection; | ||
|
||
namespace SecTester.Scan.CI; | ||
|
||
internal static class ResourceUtils | ||
{ | ||
public static string GetEmbeddedResourceContent<T>(string resourceName) | ||
where T : class | ||
{ | ||
if (string.IsNullOrWhiteSpace(resourceName)) | ||
{ | ||
throw new ArgumentNullException(nameof(resourceName)); | ||
} | ||
|
||
var assembly = typeof(T).GetTypeInfo().Assembly; | ||
using var stream = assembly.GetManifestResourceStream(resourceName); | ||
|
||
if (stream is null) | ||
{ | ||
throw new InvalidOperationException($"Could not get stream for {resourceName} resource."); | ||
} | ||
|
||
using var reader = new StreamReader(stream); | ||
|
||
return reader.ReadToEnd(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using System.Text.Json; | ||
|
||
namespace SecTester.Scan.CI; | ||
|
||
internal record Vendor(string Name, string Constant) | ||
{ | ||
public JsonElement Env { get; init; } | ||
public JsonElement Pr { get; init; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
using System.Collections; | ||
using System.Linq; | ||
using System.Text.Json; | ||
|
||
namespace SecTester.Scan.CI; | ||
|
||
internal class VendorMatcher | ||
{ | ||
private readonly IDictionary _env; | ||
|
||
public VendorMatcher(IDictionary env) | ||
{ | ||
_env = env; | ||
} | ||
|
||
public bool MatchEnvElement(JsonElement element) | ||
{ | ||
return element.ValueKind switch | ||
{ | ||
JsonValueKind.String => element.GetString() is not null && _env.Contains(element.GetString()!), | ||
JsonValueKind.Object => MatchInnerAny(element) || MatchInnerEnvIncludes(element) || | ||
MatchOwnProperties(element), | ||
JsonValueKind.Array => element.EnumerateArray() | ||
.All(x => x.ValueKind == JsonValueKind.String && x.GetString() is not null && _env.Contains(x.GetString()!)), | ||
_ => false | ||
}; | ||
} | ||
|
||
|
||
public bool MatchPrElement(JsonElement element) | ||
{ | ||
return element.ValueKind switch | ||
{ | ||
JsonValueKind.String => element.GetString() is not null && _env.Contains(element.GetString()!), | ||
JsonValueKind.Object => MatchInnerEnvNe(element) || MatchEnvElement(element), | ||
_ => false | ||
}; | ||
} | ||
|
||
private bool MatchOwnProperties(JsonElement element) | ||
{ | ||
return element.EnumerateObject().All(x => | ||
x.Value.ValueKind == JsonValueKind.String && _env.Contains(x.Name) && | ||
x.Value.ValueEquals(_env[x.Name]?.ToString())); | ||
} | ||
|
||
private bool MatchInnerAny(JsonElement element) | ||
{ | ||
var any = element.EnumerateObject().FirstOrDefault(x => x.NameEquals("any")); | ||
|
||
return any.Value.ValueKind == JsonValueKind.Array && any.Value.EnumerateArray() | ||
.Any(x => x.ValueKind == JsonValueKind.String && x.GetString() is not null && _env.Contains(x.GetString()!)); | ||
} | ||
|
||
private bool MatchInnerEnvIncludes(JsonElement element) | ||
{ | ||
var env = GetInnerPropertyValue(element, "env"); | ||
var includes = GetInnerPropertyValue(element, "includes"); | ||
|
||
if (env is null || includes is null || !_env.Contains(env)) | ||
{ | ||
return false; | ||
} | ||
|
||
var envVarValue = _env[env]?.ToString(); | ||
return envVarValue?.Contains(includes) ?? false; | ||
} | ||
|
||
private bool MatchInnerEnvNe(JsonElement element) | ||
{ | ||
var env = GetInnerPropertyValue(element, "env"); | ||
var ne = GetInnerPropertyValue(element, "ne"); | ||
|
||
if (env is null || ne is null || !_env.Contains(env) || ne != "false") | ||
{ | ||
return false; | ||
} | ||
|
||
var envVarValue = _env[env]?.ToString(); | ||
return !string.IsNullOrEmpty(envVarValue); | ||
} | ||
|
||
private static string? GetInnerPropertyValue(JsonElement element, string name) | ||
{ | ||
var property = element.EnumerateObject().FirstOrDefault(x => x.NameEquals(name)); | ||
return property.Value.ValueKind == JsonValueKind.String ? property.Value.GetString() : default; | ||
} | ||
} |
Oops, something went wrong.