diff --git a/src/Opa.Tests/Opa.Tests.csproj b/src/Opa.Tests/Opa.Tests.csproj
new file mode 100644
index 00000000..d0e3ee3a
--- /dev/null
+++ b/src/Opa.Tests/Opa.Tests.csproj
@@ -0,0 +1,39 @@
+
+
+
+
+ Squadron.Opa.Tests
+ latest
+ enable
+ false
+
+
+
+ Full
+ true
+
+
+
+ none
+ false
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/src/Opa.Tests/OpaResourceTest.cs b/src/Opa.Tests/OpaResourceTest.cs
new file mode 100644
index 00000000..804f3247
--- /dev/null
+++ b/src/Opa.Tests/OpaResourceTest.cs
@@ -0,0 +1,31 @@
+
+using System.Threading.Tasks;
+using Squadron;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Opa.Tests
+{
+ public class OpaResourceTest : IClassFixture
+ {
+ private readonly OpaResource _opaResource;
+ private readonly ITestOutputHelper _helper;
+
+ public OpaResourceTest(OpaResource opaResource, ITestOutputHelper helper)
+ {
+ _opaResource = opaResource;
+ _helper = helper;
+ }
+
+ [Fact]
+ public async Task Initializes_Container()
+ {
+ // Act
+ await _opaResource.InitializeAsync();
+ _helper.WriteLine($"Client address: {_opaResource.Client.BaseAddress}");
+
+ // Assert
+ Assert.True(true);
+ }
+ }
+}
diff --git a/src/Opa.Tests/xunit.runner.json b/src/Opa.Tests/xunit.runner.json
new file mode 100644
index 00000000..bd5fcdd2
--- /dev/null
+++ b/src/Opa.Tests/xunit.runner.json
@@ -0,0 +1,4 @@
+{
+ "appDomain": "denied",
+ "parallelizeAssembly": true
+}
\ No newline at end of file
diff --git a/src/Opa/Opa.csproj b/src/Opa/Opa.csproj
new file mode 100644
index 00000000..c6c12bbf
--- /dev/null
+++ b/src/Opa/Opa.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+ Squadron.Opa
+ latest
+ latest
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Opa/OpaDefaultOptions.cs b/src/Opa/OpaDefaultOptions.cs
new file mode 100644
index 00000000..55bbf544
--- /dev/null
+++ b/src/Opa/OpaDefaultOptions.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Squadron
+{
+ ///
+ /// Default OPA resource options
+ ///
+ public class OpaDefaultOptions : ContainerResourceOptions, IComposableResourceOption
+ {
+ public Type ResourceType => typeof(OpaResource);
+
+ ///
+ /// Configure resource options
+ ///
+ ///
+ public override void Configure(ContainerResourceBuilder builder)
+ {
+ builder
+ .Name("OPA")
+ .Image("openpolicyagent/opa:latest")
+ .InternalPort(8181);
+ }
+ }
+}
+
diff --git a/src/Opa/OpaResource.cs b/src/Opa/OpaResource.cs
new file mode 100644
index 00000000..7782f94a
--- /dev/null
+++ b/src/Opa/OpaResource.cs
@@ -0,0 +1,75 @@
+
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Xunit;
+using System.Text.Json;
+using System.IO;
+using System.Net.Http.Headers;
+
+namespace Squadron
+{
+
+ ///
+ public class OpaResource : OpaResource { }
+
+ ///
+ /// Represents a mongo database resource that can be used by unit tests.
+ ///
+ ///
+ public class OpaResource
+ : ContainerResource,
+ IAsyncLifetime,
+ IComposableResource
+ where TOptions : ContainerResourceOptions, new()
+ {
+ private HttpClient _client = new();
+ public HttpClient Client => _client;
+
+ ///
+ public override async Task InitializeAsync()
+ {
+ await base.InitializeAsync();
+ var baseAddress =
+ $"http://{Manager.Instance.Address}:{Manager.Instance.HostPort}";
+
+ _client = GetClient(baseAddress);
+ await Initializer.WaitAsync(new OpaStatus(_client));
+ }
+
+ public async Task ListPolicies()
+ {
+ Stream contentStream = await ListPolicies();
+ T? result = await JsonSerializer.DeserializeAsync(contentStream);
+
+ return result;
+ }
+
+ public async Task ListPolicies()
+ {
+ return await _client.GetStreamAsync("/v1/policies");
+ }
+
+ public async Task SetPolicy(string policyContent)
+ {
+ HttpContent content = new StringContent(policyContent);
+ await _client.PutAsync("/v1/policies", content);
+ }
+
+ public async Task EvalPolicy(string policyId, string input)
+ {
+ HttpContent content = new StringContent(input);
+ content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
+
+ await _client.PostAsync($"/v1/data/{policyId}", content);
+ }
+
+ private HttpClient GetClient(string baseAddress)
+ {
+ return new()
+ {
+ BaseAddress = new Uri(baseAddress),
+ };
+ }
+ }
+}
diff --git a/src/Opa/OpaStatus.cs b/src/Opa/OpaStatus.cs
new file mode 100644
index 00000000..97671d92
--- /dev/null
+++ b/src/Opa/OpaStatus.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Squadron
+{
+ ///
+ /// Status checker for Nats
+ ///
+ ///
+ public class OpaStatus : IResourceStatusProvider, IDisposable
+ {
+ private readonly HttpClient _httpClient;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Hostname
+ public OpaStatus(HttpClient httpClient)
+ {
+ _httpClient = httpClient;
+ }
+
+ ///
+ public async Task IsReadyAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ // HttpResponseMessage? response = await _httpClient.GetAsync(
+ // new Uri("health", UriKind.Relative), cancellationToken);
+
+ // Debug.Assert(response?.StatusCode == HttpStatusCode.OK);
+
+ return new Status
+ {
+ IsReady = true,
+ Message = "OPA is ready!"
+ };
+
+ }
+ catch (Exception ex)
+ {
+ return new Status
+ {
+ IsReady = false,
+ Message = ex.Message
+ };
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ _httpClient.Dispose();
+ }
+ }
+}
diff --git a/src/Squadron.sln b/src/Squadron.sln
index 59b89798..1ef33c41 100644
--- a/src/Squadron.sln
+++ b/src/Squadron.sln
@@ -1,4 +1,4 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32319.34
MinimumVisualStudioVersion = 15.0.26124.0
@@ -86,6 +86,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nats", "Nats\Nats.csproj",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nats.Tests", "Nats.Tests\Nats.Tests.csproj", "{2164CEEC-9F13-44D3-95B9-ED81AAC74189}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Opa", "Opa\Opa.csproj", "{7272EB8A-6DF2-40E3-B747-C3F592F91CAA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Opa.Tests", "Opa.Tests\Opa.Tests.csproj", "{CBF15F2A-AFB3-4908-9EFA-2D71646860CC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -588,6 +592,30 @@ Global
{2164CEEC-9F13-44D3-95B9-ED81AAC74189}.Release|x64.Build.0 = Release|Any CPU
{2164CEEC-9F13-44D3-95B9-ED81AAC74189}.Release|x86.ActiveCfg = Release|Any CPU
{2164CEEC-9F13-44D3-95B9-ED81AAC74189}.Release|x86.Build.0 = Release|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Debug|x64.Build.0 = Debug|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Debug|x86.Build.0 = Debug|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Release|x64.ActiveCfg = Release|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Release|x64.Build.0 = Release|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Release|x86.ActiveCfg = Release|Any CPU
+ {7272EB8A-6DF2-40E3-B747-C3F592F91CAA}.Release|x86.Build.0 = Release|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Debug|x64.Build.0 = Debug|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Debug|x86.Build.0 = Debug|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Release|x64.ActiveCfg = Release|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Release|x64.Build.0 = Release|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Release|x86.ActiveCfg = Release|Any CPU
+ {CBF15F2A-AFB3-4908-9EFA-2D71646860CC}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE