• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

me-viper / OpaDotNet.Compilation / 6942841257

21 Nov 2023 09:53AM UTC coverage: 88.266%. Remained the same
6942841257

push

github

me-viper
chore: Update CHANGELOG

180 of 220 branches covered (0.0%)

Branch coverage included in aggregate %.

670 of 743 relevant lines covered (90.17%)

533.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.0
/src/OpaDotNet.Compilation.Abstractions/BundleWriter.cs
1
using System.Formats.Tar;
2
using System.IO.Compression;
3
using System.Runtime.InteropServices;
4
using System.Text;
5
using System.Text.Json;
6
using System.Text.Json.Nodes;
7

8
using JetBrains.Annotations;
9

10
using Microsoft.Extensions.FileSystemGlobbing;
11
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
12

13
namespace OpaDotNet.Compilation.Abstractions;
14

15
/// <summary>
16
/// Implements writing files into OPA policy bundle.
17
/// </summary>
18
/// <remarks>You need to dispose <see cref="BundleWriter"/> instance before you can use resulting bundle.</remarks>
19
/// <example>
20
/// <code>
21
/// using var ms = new MemoryStream();
22
///
23
/// using (var writer = new BundleWriter(ms))
24
/// {
25
///     writer.WriteEntry("package test", "policy.rego");
26
/// }
27
///
28
/// // Now bundle have been constructed.
29
/// ms.Seek(0, SeekOrigin.Begin);
30
/// ...
31
/// </code>
32
/// </example>
33
[PublicAPI]
34
public sealed class BundleWriter : IDisposable, IAsyncDisposable
35
{
36
    private readonly TarWriter _writer;
37

38
    /// <summary>
39
    /// <c>true</c> if BundleWriter has no entries written; otherwise <c>false</c>.
40
    /// </summary>
41
    public bool IsEmpty { get; private set; }
128✔
42

43
    /// <summary>
44
    /// Creates new instance of <see cref="BundleWriter"/>.
45
    /// </summary>
46
    /// <param name="stream">Stream to write bundle to.</param>
47
    /// <param name="manifest">Policy bundle manifest.</param>
48
    public BundleWriter(Stream stream, BundleManifest? manifest = null)
44✔
49
    {
44✔
50
        ArgumentNullException.ThrowIfNull(stream);
44✔
51

52
        var zip = new GZipStream(stream, CompressionMode.Compress, true);
44✔
53
        _writer = new TarWriter(zip);
44✔
54

55
        if (manifest != null)
44✔
56
            WriteEntry(JsonSerializer.Serialize(manifest), ".manifest");
4✔
57
    }
44✔
58

59
    private static string NormalizePath(string path) => "/" + path.Replace("\\", "/").TrimStart('/');
112✔
60

61
    /// <summary>
62
    /// Merges two capabilities.json streams.
63
    /// </summary>
64
    /// <param name="caps1">First capabilities.json stream.</param>
65
    /// <param name="caps2">Second capabilities.json stream.</param>
66
    /// <returns>Merged capabilities.json stream.</returns>
67
    public static Stream MergeCapabilities(Stream caps1, Stream caps2)
68
    {
20✔
69
        ArgumentNullException.ThrowIfNull(caps1);
20✔
70
        ArgumentNullException.ThrowIfNull(caps2);
20✔
71

72
        var resultDoc = JsonNode.Parse(caps1);
20✔
73

74
        if (resultDoc == null)
20!
75
            throw new RegoCompilationException("Failed to parse capabilities file");
×
76

77
        var resultBins = resultDoc.Root["builtins"]?.AsArray();
20!
78

79
        if (resultBins == null)
20!
80
            throw new RegoCompilationException("Invalid capabilities file: 'builtins' node not found");
×
81

82
        var capsDoc = JsonDocument.Parse(caps2);
20✔
83
        var capsBins = capsDoc.RootElement.GetProperty("builtins");
20✔
84

85
        foreach (var bin in capsBins.EnumerateArray())
108✔
86
        {
24✔
87
            JsonNode? node = bin.ValueKind switch
24!
88
            {
24✔
89
                JsonValueKind.Array => JsonArray.Create(bin),
×
90
                JsonValueKind.Object => JsonObject.Create(bin),
24✔
91
                _ => JsonValue.Create(bin)
×
92
            };
24✔
93

94
            resultBins.Add(node);
24✔
95
        }
24✔
96

97
        var ms = new MemoryStream();
20✔
98

99
        using (var jw = new Utf8JsonWriter(ms))
20✔
100
        {
20✔
101
            resultDoc.WriteTo(jw);
20✔
102
        }
20✔
103

104
        ms.Seek(0, SeekOrigin.Begin);
20✔
105

106
        return ms;
20✔
107
    }
20✔
108

109
    /// <summary>
110
    /// Creates new instance of <see cref="BundleWriter"/> and populates it with files in <paramref name="path"/>.
111
    /// </summary>
112
    /// <param name="stream">Stream to write bundle to.</param>
113
    /// <param name="path">Path containing bundle source files.</param>
114
    /// <param name="exclusions">File name patterns for files the BundleWriter should exclude from the results.</param>
115
    public static BundleWriter FromDirectory(
116
        Stream stream,
117
        string path,
118
        IReadOnlySet<string>? exclusions)
119
    {
16✔
120
        var di = new DirectoryInfo(path);
16✔
121

122
        if (!di.Exists)
16!
123
            throw new DirectoryNotFoundException($"Directory {di.FullName} was not found");
×
124

125
        var comparison = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
16!
126
            ? StringComparison.OrdinalIgnoreCase
16✔
127
            : StringComparison.Ordinal;
16✔
128

129
        var glob = new Matcher(comparison);
16✔
130
        glob.AddInclude("**/data.json");
16✔
131
        glob.AddInclude("**/data.yaml");
16✔
132
        glob.AddInclude("**/*.rego");
16✔
133
        glob.AddInclude("**/policy.wasm");
16✔
134
        glob.AddInclude("**/.manifest");
16✔
135

136
        if (exclusions != null)
16✔
137
        {
12✔
138
            foreach (var excl in exclusions)
68✔
139
                glob.AddExclude(excl);
16✔
140
        }
12✔
141

142
        var matches = glob.Execute(new DirectoryInfoWrapper(di));
16✔
143

144
        var writer = new BundleWriter(stream);
16✔
145

146
        foreach (var file in matches.Files)
160✔
147
        {
56✔
148
            var filePath = string.IsNullOrWhiteSpace(file.Stem) || string.Equals(file.Path, file.Stem, StringComparison.Ordinal)
56!
149
                ? file.Path
56✔
150
                : Path.Combine(file.Path, file.Stem);
56✔
151

152
            var fullPath = Path.Combine(di.FullName, filePath);
56✔
153

154
            using var fs = new FileStream(fullPath, FileMode.Open, FileAccess.Read);
56✔
155
            writer.WriteEntry(fs, filePath);
56✔
156
        }
56✔
157

158
        return writer;
16✔
159
    }
16✔
160

161
    /// <summary>
162
    /// Writes string content into bundle.
163
    /// </summary>
164
    /// <param name="str">String content.</param>
165
    /// <param name="path">Relative file path inside bundle.</param>
166
    public void WriteEntry(ReadOnlySpan<char> str, string path)
167
    {
20✔
168
        ArgumentException.ThrowIfNullOrEmpty(path);
20✔
169

170
        Span<byte> bytes = new byte[Encoding.UTF8.GetByteCount(str)];
20✔
171
        _ = Encoding.UTF8.GetBytes(str, bytes);
20✔
172

173
        WriteEntry(bytes, path);
20✔
174
    }
20✔
175

176
    /// <summary>
177
    /// Writes bytes content into bundle.
178
    /// </summary>
179
    /// <param name="bytes">String content.</param>
180
    /// <param name="path">Relative file path inside bundle.</param>
181
    public void WriteEntry(ReadOnlySpan<byte> bytes, string path)
182
    {
44✔
183
        ArgumentException.ThrowIfNullOrEmpty(path);
44✔
184

185
        using var ms = new MemoryStream(bytes.Length);
44✔
186
        ms.Write(bytes);
44✔
187
        ms.Seek(0, SeekOrigin.Begin);
44✔
188

189
        WriteEntry(ms, path);
44✔
190
    }
88✔
191

192
    /// <summary>
193
    /// Writes stream content into bundle.
194
    /// </summary>
195
    /// <param name="stream">String content.</param>
196
    /// <param name="path">Relative file path inside bundle.</param>
197
    public void WriteEntry(Stream stream, string path)
198
    {
112✔
199
        ArgumentNullException.ThrowIfNull(stream);
112✔
200
        ArgumentException.ThrowIfNullOrEmpty(path);
112✔
201

202
        if (Path.IsPathRooted(path))
112✔
203
        {
20✔
204
            if (Path.GetPathRoot(path)?[0] != Path.DirectorySeparatorChar)
20!
205
                path = path[2..];
×
206
        }
20✔
207

208
        var entry = new PaxTarEntry(TarEntryType.RegularFile, NormalizePath(path))
112✔
209
        {
112✔
210
            DataStream = stream,
112✔
211
        };
112✔
212

213
        _writer.WriteEntry(entry);
112✔
214
        IsEmpty = false;
112✔
215
    }
112✔
216

217
    /// <inheritdoc />
218
    public void Dispose()
219
    {
×
220
        _writer.Dispose();
×
221
    }
×
222

223
    /// <inheritdoc />
224
    public async ValueTask DisposeAsync()
225
    {
44✔
226
        await _writer.DisposeAsync().ConfigureAwait(false);
44✔
227
    }
44✔
228
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc