Skip to content

Commit 29d485b

Browse files
authored
chore: add Vpn.Service app for Manager (#9)
1 parent 559b111 commit 29d485b

40 files changed

+2372
-345
lines changed

Coder.Desktop.sln

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Vpn", "Vpn\Vp
55
EndProject
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Vpn.Proto", "Vpn.Proto\Vpn.Proto.csproj", "{318E78BB-E6AD-410F-8F3F-B680F6880293}"
77
EndProject
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Tests", "Tests\Tests.csproj", "{D247B2E7-38A0-4A69-A710-7E8FAA7B807E}"
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Vpn.Service", "Vpn.Service\Vpn.Service.csproj", "{51B91794-0A2A-4F84-9935-8E17DD2AB260}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Tests.Vpn", "Tests.Vpn\Tests.Vpn.csproj", "{D247B2E7-38A0-4A69-A710-7E8FAA7B807E}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Tests.Vpn.Proto", "Tests.Vpn.Proto\Tests.Vpn.Proto.csproj", "{AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}"
13+
EndProject
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.Tests.Vpn.Service", "Tests.Vpn.Service\Tests.Vpn.Service.csproj", "{D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}"
15+
EndProject
16+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coder.Desktop.CoderSdk", "CoderSdk\CoderSdk.csproj", "{A3D2B2B3-A051-46BD-A190-5487A9F24C28}"
917
EndProject
1018
Global
1119
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -21,9 +29,25 @@ Global
2129
{318E78BB-E6AD-410F-8F3F-B680F6880293}.Debug|Any CPU.Build.0 = Debug|Any CPU
2230
{318E78BB-E6AD-410F-8F3F-B680F6880293}.Release|Any CPU.ActiveCfg = Release|Any CPU
2331
{318E78BB-E6AD-410F-8F3F-B680F6880293}.Release|Any CPU.Build.0 = Release|Any CPU
32+
{51B91794-0A2A-4F84-9935-8E17DD2AB260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33+
{51B91794-0A2A-4F84-9935-8E17DD2AB260}.Debug|Any CPU.Build.0 = Debug|Any CPU
34+
{51B91794-0A2A-4F84-9935-8E17DD2AB260}.Release|Any CPU.ActiveCfg = Release|Any CPU
35+
{51B91794-0A2A-4F84-9935-8E17DD2AB260}.Release|Any CPU.Build.0 = Release|Any CPU
2436
{D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2537
{D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Debug|Any CPU.Build.0 = Debug|Any CPU
2638
{D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Release|Any CPU.ActiveCfg = Release|Any CPU
2739
{D247B2E7-38A0-4A69-A710-7E8FAA7B807E}.Release|Any CPU.Build.0 = Release|Any CPU
40+
{AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41+
{AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
42+
{AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
43+
{AA3EEFF4-414B-4A83-8ACF-188C3C61CCE1}.Release|Any CPU.Build.0 = Release|Any CPU
44+
{D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45+
{D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
46+
{D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
47+
{D32E5FE1-C251-4A08-8EBE-B8D4F18A36F1}.Release|Any CPU.Build.0 = Release|Any CPU
48+
{A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49+
{A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Debug|Any CPU.Build.0 = Debug|Any CPU
50+
{A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|Any CPU.ActiveCfg = Release|Any CPU
51+
{A3D2B2B3-A051-46BD-A190-5487A9F24C28}.Release|Any CPU.Build.0 = Release|Any CPU
2852
EndGlobalSection
2953
EndGlobal

Coder.Desktop.sln.DotSettings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,7 @@
253253
</Patterns>
254254
</s:String>
255255
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002EMemberReordering_002EMigrations_002ECSharpFileLayoutPatternRemoveIsAttributeUpgrade/@EntryIndexedValue">True</s:Boolean>
256+
<s:Boolean x:Key="/Default/UserDictionary/Words/=codervpn/@EntryIndexedValue">True</s:Boolean>
257+
<s:Boolean x:Key="/Default/UserDictionary/Words/=hkey/@EntryIndexedValue">True</s:Boolean>
258+
<s:Boolean x:Key="/Default/UserDictionary/Words/=replyable/@EntryIndexedValue">True</s:Boolean>
256259
<s:Boolean x:Key="/Default/UserDictionary/Words/=serdes/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

CoderSdk/CoderApiClient.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System.Text;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
5+
namespace CoderSdk;
6+
7+
/// <summary>
8+
/// Changes names from PascalCase to snake_case.
9+
/// </summary>
10+
internal class SnakeCaseNamingPolicy : JsonNamingPolicy
11+
{
12+
public override string ConvertName(string name)
13+
{
14+
return string.Concat(
15+
name.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + char.ToLower(x) : char.ToLower(x).ToString())
16+
);
17+
}
18+
}
19+
20+
/// <summary>
21+
/// Provides a limited selection of API methods for a Coder instance.
22+
/// </summary>
23+
public partial class CoderApiClient
24+
{
25+
// TODO: allow adding headers
26+
private readonly HttpClient _httpClient = new();
27+
private readonly JsonSerializerOptions _jsonOptions;
28+
29+
public CoderApiClient(string baseUrl)
30+
{
31+
var url = new Uri(baseUrl, UriKind.Absolute);
32+
if (url.PathAndQuery != "/")
33+
throw new ArgumentException($"Base URL '{baseUrl}' must not contain a path", nameof(baseUrl));
34+
_httpClient.BaseAddress = url;
35+
_jsonOptions = new JsonSerializerOptions
36+
{
37+
PropertyNameCaseInsensitive = true,
38+
PropertyNamingPolicy = new SnakeCaseNamingPolicy(),
39+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
40+
};
41+
}
42+
43+
public CoderApiClient(string baseUrl, string token) : this(baseUrl)
44+
{
45+
SetSessionToken(token);
46+
}
47+
48+
public void SetSessionToken(string token)
49+
{
50+
_httpClient.DefaultRequestHeaders.Remove("Coder-Session-Token");
51+
_httpClient.DefaultRequestHeaders.Add("Coder-Session-Token", token);
52+
}
53+
54+
private async Task<TResponse> SendRequestAsync<TResponse>(HttpMethod method, string path,
55+
object? payload, CancellationToken ct = default)
56+
{
57+
try
58+
{
59+
var request = new HttpRequestMessage(method, path);
60+
61+
if (payload is not null)
62+
{
63+
var json = JsonSerializer.Serialize(payload, _jsonOptions);
64+
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
65+
}
66+
67+
var res = await _httpClient.SendAsync(request, ct);
68+
// TODO: this should be improved to try and parse a codersdk.Error response
69+
res.EnsureSuccessStatusCode();
70+
71+
var content = await res.Content.ReadAsStringAsync(ct);
72+
var data = JsonSerializer.Deserialize<TResponse>(content, _jsonOptions);
73+
if (data is null) throw new JsonException("Deserialized response is null");
74+
return data;
75+
}
76+
catch (Exception e)
77+
{
78+
throw new Exception($"API Request: {method} {path} (req body: {payload is not null})", e);
79+
}
80+
}
81+
}

CoderSdk/CoderSdk.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
</Project>

CoderSdk/Deployment.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace CoderSdk;
2+
3+
public class BuildInfo
4+
{
5+
public string ExternalUrl { get; set; } = "";
6+
public string Version { get; set; } = "";
7+
public string DashboardUrl { get; set; } = "";
8+
public bool Telemetry { get; set; } = false;
9+
public bool WorkspaceProxy { get; set; } = false;
10+
public string AgentApiVersion { get; set; } = "";
11+
public string ProvisionerApiVersion { get; set; } = "";
12+
public string UpgradeMessage { get; set; } = "";
13+
public string DeploymentId { get; set; } = "";
14+
}
15+
16+
public partial class CoderApiClient
17+
{
18+
public Task<BuildInfo> GetBuildInfo(CancellationToken ct = default)
19+
{
20+
return SendRequestAsync<BuildInfo>(HttpMethod.Get, "/api/v2/buildinfo", null, ct);
21+
}
22+
}

CoderSdk/Users.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace CoderSdk;
2+
3+
public class User
4+
{
5+
public const string Me = "me";
6+
7+
// TODO: fill out more fields
8+
public string Username { get; set; } = "";
9+
}
10+
11+
public partial class CoderApiClient
12+
{
13+
public Task<User> GetUser(string user, CancellationToken ct = default)
14+
{
15+
return SendRequestAsync<User>(HttpMethod.Get, $"/api/v2/users/{user}", null, ct);
16+
}
17+
}

Tests/Vpn.Proto/RpcHeaderTest.cs renamed to Tests.Vpn.Proto/RpcHeaderTest.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ public void Valid()
1111
{
1212
var headerStr = "codervpn manager 1.3,2.1";
1313
var header = RpcHeader.Parse(headerStr);
14-
Assert.That(header.Role.ToString(), Is.EqualTo(RpcRole.Manager));
14+
Assert.That(header.Role, Is.EqualTo("manager"));
1515
Assert.That(header.VersionList, Is.EqualTo(new RpcVersionList(new RpcVersion(1, 3), new RpcVersion(2, 1))));
1616
Assert.That(header.ToString(), Is.EqualTo(headerStr + "\n"));
1717
Assert.That(header.ToBytes().ToArray(), Is.EqualTo(Encoding.UTF8.GetBytes(headerStr + "\n")));
1818

1919
headerStr = "codervpn tunnel 1.0";
2020
header = RpcHeader.Parse(headerStr);
21-
Assert.That(header.Role.ToString(), Is.EqualTo(RpcRole.Tunnel));
21+
Assert.That(header.Role, Is.EqualTo("tunnel"));
2222
Assert.That(header.VersionList, Is.EqualTo(new RpcVersionList(new RpcVersion(1, 0))));
2323
Assert.That(header.ToString(), Is.EqualTo(headerStr + "\n"));
2424
Assert.That(header.ToBytes().ToArray(), Is.EqualTo(Encoding.UTF8.GetBytes(headerStr + "\n")));
@@ -35,7 +35,8 @@ public void ParseInvalid()
3535
Assert.That(ex.Message, Does.Contain("Wrong number of parts"));
3636
ex = Assert.Throws<ArgumentException>(() => RpcHeader.Parse("cats manager 1.0"));
3737
Assert.That(ex.Message, Does.Contain("Invalid preamble"));
38-
ex = Assert.Throws<ArgumentException>(() => RpcHeader.Parse("codervpn cats 1.0"));
39-
Assert.That(ex.Message, Does.Contain("Unknown role 'cats'"));
38+
// RpcHeader doesn't care about the role string as long as it isn't empty.
39+
ex = Assert.Throws<ArgumentException>(() => RpcHeader.Parse("codervpn 1.0"));
40+
Assert.That(ex.Message, Does.Contain("Invalid role in header string"));
4041
}
4142
}

Tests.Vpn.Proto/RpcMessageTest.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Coder.Desktop.Vpn.Proto;
2+
3+
namespace Coder.Desktop.Tests.Vpn.Proto;
4+
5+
[TestFixture]
6+
public class RpcRoleAttributeTest
7+
{
8+
[Test]
9+
public void Ok()
10+
{
11+
var role = new RpcRoleAttribute("manager");
12+
Assert.That(role.Role, Is.EqualTo("manager"));
13+
role = new RpcRoleAttribute("tunnel");
14+
Assert.That(role.Role, Is.EqualTo("tunnel"));
15+
role = new RpcRoleAttribute("service");
16+
Assert.That(role.Role, Is.EqualTo("service"));
17+
role = new RpcRoleAttribute("client");
18+
Assert.That(role.Role, Is.EqualTo("client"));
19+
}
20+
}
21+
22+
[TestFixture]
23+
public class RpcMessageTest
24+
{
25+
[Test]
26+
public void GetRole()
27+
{
28+
// RpcMessage<RPC> is not a supported message type and doesn't have an
29+
// RpcRoleAttribute
30+
var ex = Assert.Throws<ArgumentException>(() => _ = RpcMessage<RPC>.GetRole());
31+
Assert.That(ex.Message,
32+
Does.Contain("Message type 'Coder.Desktop.Vpn.Proto.RPC' does not have a RpcRoleAttribute"));
33+
34+
Assert.That(ManagerMessage.GetRole(), Is.EqualTo("manager"));
35+
Assert.That(TunnelMessage.GetRole(), Is.EqualTo("tunnel"));
36+
Assert.That(ServiceMessage.GetRole(), Is.EqualTo("service"));
37+
Assert.That(ClientMessage.GetRole(), Is.EqualTo("client"));
38+
}
39+
}
File renamed without changes.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<RootNamespace>Coder.Desktop.Tests.Vpn.Proto</RootNamespace>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
9+
<IsPackable>false</IsPackable>
10+
<IsTestProject>true</IsTestProject>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="coverlet.collector" Version="6.0.2">
15+
<PrivateAssets>all</PrivateAssets>
16+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17+
</PackageReference>
18+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
19+
<PackageReference Include="NUnit" Version="4.2.2"/>
20+
<PackageReference Include="NUnit.Analyzers" Version="4.4.0">
21+
<PrivateAssets>all</PrivateAssets>
22+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
23+
</PackageReference>
24+
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/>
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<Using Include="NUnit.Framework"/>
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<ProjectReference Include="..\Vpn.Proto\Vpn.Proto.csproj"/>
33+
</ItemGroup>
34+
35+
</Project>

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy