130 lines
4.1 KiB
C#
130 lines
4.1 KiB
C#
using System.Collections.Immutable;
|
|
using System.IO;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace StellaOps.Scanner.EntryTrace;
|
|
|
|
/// <summary>
|
|
/// Represents the deserialized OCI image config document.
|
|
/// </summary>
|
|
internal sealed class OciImageConfiguration
|
|
{
|
|
[JsonPropertyName("config")]
|
|
public OciImageConfig? Config { get; init; }
|
|
|
|
[JsonPropertyName("container_config")]
|
|
public OciImageConfig? ContainerConfig { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logical representation of the OCI image config fields used by EntryTrace.
|
|
/// </summary>
|
|
public sealed class OciImageConfig
|
|
{
|
|
[JsonPropertyName("Env")]
|
|
[JsonConverter(typeof(FlexibleStringListConverter))]
|
|
public ImmutableArray<string> Environment { get; init; } = ImmutableArray<string>.Empty;
|
|
|
|
[JsonPropertyName("Entrypoint")]
|
|
[JsonConverter(typeof(FlexibleStringListConverter))]
|
|
public ImmutableArray<string> Entrypoint { get; init; } = ImmutableArray<string>.Empty;
|
|
|
|
[JsonPropertyName("Cmd")]
|
|
[JsonConverter(typeof(FlexibleStringListConverter))]
|
|
public ImmutableArray<string> Command { get; init; } = ImmutableArray<string>.Empty;
|
|
|
|
[JsonPropertyName("WorkingDir")]
|
|
public string? WorkingDirectory { get; init; }
|
|
|
|
[JsonPropertyName("User")]
|
|
public string? User { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads <see cref="OciImageConfig"/> instances from OCI config JSON.
|
|
/// </summary>
|
|
public static class OciImageConfigLoader
|
|
{
|
|
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
|
{
|
|
PropertyNameCaseInsensitive = true
|
|
};
|
|
|
|
public static OciImageConfig Load(string filePath)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(filePath);
|
|
using var stream = File.OpenRead(filePath);
|
|
return Load(stream);
|
|
}
|
|
|
|
public static OciImageConfig Load(Stream stream)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(stream);
|
|
|
|
var configuration = JsonSerializer.Deserialize<OciImageConfiguration>(stream, SerializerOptions)
|
|
?? throw new InvalidDataException("OCI image config is empty or invalid.");
|
|
|
|
if (configuration.Config is not null)
|
|
{
|
|
return configuration.Config;
|
|
}
|
|
|
|
if (configuration.ContainerConfig is not null)
|
|
{
|
|
return configuration.ContainerConfig;
|
|
}
|
|
|
|
throw new InvalidDataException("OCI image config does not include a config section.");
|
|
}
|
|
}
|
|
|
|
internal sealed class FlexibleStringListConverter : JsonConverter<ImmutableArray<string>>
|
|
{
|
|
public override ImmutableArray<string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
{
|
|
if (reader.TokenType == JsonTokenType.Null)
|
|
{
|
|
return ImmutableArray<string>.Empty;
|
|
}
|
|
|
|
if (reader.TokenType == JsonTokenType.StartArray)
|
|
{
|
|
var builder = ImmutableArray.CreateBuilder<string>();
|
|
while (reader.Read())
|
|
{
|
|
if (reader.TokenType == JsonTokenType.EndArray)
|
|
{
|
|
return builder.ToImmutable();
|
|
}
|
|
|
|
if (reader.TokenType == JsonTokenType.String)
|
|
{
|
|
builder.Add(reader.GetString() ?? string.Empty);
|
|
continue;
|
|
}
|
|
|
|
throw new JsonException($"Expected string elements in array but found {reader.TokenType}.");
|
|
}
|
|
}
|
|
|
|
if (reader.TokenType == JsonTokenType.String)
|
|
{
|
|
return ImmutableArray.Create(reader.GetString() ?? string.Empty);
|
|
}
|
|
|
|
throw new JsonException($"Unsupported JSON token {reader.TokenType} for string array.");
|
|
}
|
|
|
|
public override void Write(Utf8JsonWriter writer, ImmutableArray<string> value, JsonSerializerOptions options)
|
|
{
|
|
writer.WriteStartArray();
|
|
foreach (var entry in value)
|
|
{
|
|
writer.WriteStringValue(entry);
|
|
}
|
|
|
|
writer.WriteEndArray();
|
|
}
|
|
}
|