Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitbe72f80

Browse files
authored
feat: fetch hostname suffix from API (#103)
Fixes#49Adds support to query the hostname suffix from Coder server, and thenpropagates any changes to the agent view models.
1 parent9e50acd commitbe72f80

File tree

7 files changed

+316
-6
lines changed

7 files changed

+316
-6
lines changed

‎App/App.xaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public App()
7272
newWindowsCredentialBackend(WindowsCredentialBackend.CoderCredentialsTargetName));
7373
services.AddSingleton<ICredentialManager,CredentialManager>();
7474
services.AddSingleton<IRpcController,RpcController>();
75+
services.AddSingleton<IHostnameSuffixGetter,HostnameSuffixGetter>();
7576

7677
services.AddOptions<MutagenControllerConfig>()
7778
.Bind(builder.Configuration.GetSection(MutagenControllerConfigSection));

‎App/Models/CredentialModel.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
usingSystem;
2+
usingCoder.Desktop.CoderSdk.Coder;
23

34
namespaceCoder.Desktop.App.Models;
45

@@ -14,7 +15,7 @@ public enum CredentialState
1415
Valid,
1516
}
1617

17-
publicclassCredentialModel
18+
publicclassCredentialModel:ICoderApiClientCredentialProvider
1819
{
1920
publicCredentialStateState{get;init;}=CredentialState.Unknown;
2021

@@ -33,4 +34,14 @@ public CredentialModel Clone()
3334
Username=Username,
3435
};
3536
}
37+
38+
publicCoderApiClientCredential?GetCoderApiClientCredential()
39+
{
40+
if(State!=CredentialState.Valid)returnnull;
41+
returnnewCoderApiClientCredential
42+
{
43+
ApiToken=ApiToken!,
44+
CoderUrl=CoderUrl!,
45+
};
46+
}
3647
}

‎App/Services/HostnameSuffixGetter.cs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
usingSystem;
2+
usingSystem.Threading;
3+
usingSystem.Threading.Tasks;
4+
usingCoder.Desktop.App.Models;
5+
usingCoder.Desktop.CoderSdk.Coder;
6+
usingCoder.Desktop.Vpn.Utilities;
7+
usingMicrosoft.Extensions.Logging;
8+
9+
namespaceCoder.Desktop.App.Services;
10+
11+
publicinterfaceIHostnameSuffixGetter
12+
{
13+
publiceventEventHandler<string>SuffixChanged;
14+
15+
publicstringGetCachedSuffix();
16+
}
17+
18+
publicclassHostnameSuffixGetter:IHostnameSuffixGetter
19+
{
20+
privateconststringDefaultSuffix=".coder";
21+
22+
privatereadonlyICredentialManager_credentialManager;
23+
privatereadonlyICoderApiClientFactory_clientFactory;
24+
privatereadonlyILogger<HostnameSuffixGetter>_logger;
25+
26+
// _lock protects all private (non-readonly) values
27+
privatereadonlyRaiiSemaphoreSlim_lock=new(1,1);
28+
privatestring_domainSuffix=DefaultSuffix;
29+
privatebool_dirty=false;
30+
privatebool_getInProgress=false;
31+
privateCredentialModel_credentialModel=new(){State=CredentialState.Invalid};
32+
33+
publiceventEventHandler<string>?SuffixChanged;
34+
35+
publicHostnameSuffixGetter(ICredentialManagercredentialManager,ICoderApiClientFactoryapiClientFactory,
36+
ILogger<HostnameSuffixGetter>logger)
37+
{
38+
_credentialManager=credentialManager;
39+
_clientFactory=apiClientFactory;
40+
_logger=logger;
41+
credentialManager.CredentialsChanged+=HandleCredentialsChanged;
42+
HandleCredentialsChanged(this,_credentialManager.GetCachedCredentials());
43+
}
44+
45+
~HostnameSuffixGetter()
46+
{
47+
_credentialManager.CredentialsChanged-=HandleCredentialsChanged;
48+
}
49+
50+
privatevoidHandleCredentialsChanged(object?sender,CredentialModelcredentials)
51+
{
52+
usingvar_=_lock.Lock();
53+
_logger.LogDebug("credentials updated with state {state}",credentials.State);
54+
_credentialModel=credentials;
55+
if(credentials.State!=CredentialState.Valid)return;
56+
57+
_dirty=true;
58+
if(!_getInProgress)
59+
{
60+
_getInProgress=true;
61+
Task.Run(Refresh).ContinueWith(MaybeRefreshAgain);
62+
}
63+
}
64+
65+
privateasyncTaskRefresh()
66+
{
67+
_logger.LogDebug("refreshing domain suffix");
68+
CredentialModelcredentials;
69+
using(_=await_lock.LockAsync())
70+
{
71+
credentials=_credentialModel;
72+
if(credentials.State!=CredentialState.Valid)
73+
{
74+
_logger.LogDebug("abandoning refresh because credentials are now invalid");
75+
return;
76+
}
77+
78+
_dirty=false;
79+
}
80+
81+
varclient=_clientFactory.Create(credentials);
82+
usingvartimeoutSrc=newCancellationTokenSource(TimeSpan.FromSeconds(10));
83+
varconnInfo=awaitclient.GetAgentConnectionInfoGeneric(timeoutSrc.Token);
84+
85+
// older versions of Coder might not set this
86+
varsuffix=string.IsNullOrEmpty(connInfo.HostnameSuffix)
87+
?DefaultSuffix
88+
// and, it doesn't include the leading dot.
89+
:"."+connInfo.HostnameSuffix;
90+
91+
varchanged=false;
92+
using(_=await_lock.LockAsync(CancellationToken.None))
93+
{
94+
if(_domainSuffix!=suffix)changed=true;
95+
_domainSuffix=suffix;
96+
}
97+
98+
if(changed)
99+
{
100+
_logger.LogInformation("got new domain suffix '{suffix}'",suffix);
101+
// grab a local copy of the EventHandler to avoid TOCTOU race on the `?.` null-check
102+
vardel=SuffixChanged;
103+
del?.Invoke(this,suffix);
104+
}
105+
else
106+
{
107+
_logger.LogDebug("domain suffix unchanged '{suffix}'",suffix);
108+
}
109+
}
110+
111+
privateasyncTaskMaybeRefreshAgain(Taskprev)
112+
{
113+
if(prev.IsFaulted)
114+
{
115+
_logger.LogError(prev.Exception,"failed to query domain suffix");
116+
// back off here before retrying. We're just going to use a fixed, long
117+
// delay since this just affects UI stuff; we're not in a huge rush as
118+
// long as we eventually get the right value.
119+
awaitTask.Delay(TimeSpan.FromSeconds(10));
120+
}
121+
122+
usingvarl=await_lock.LockAsync(CancellationToken.None);
123+
if((_dirty||prev.IsFaulted)&&_credentialModel.State==CredentialState.Valid)
124+
{
125+
// we still have valid credentials and we're either dirty or the last Get failed.
126+
_logger.LogDebug("retrying domain suffix query");
127+
_=Task.Run(Refresh).ContinueWith(MaybeRefreshAgain);
128+
return;
129+
}
130+
131+
// Getting here means either the credentials are not valid or we don't need to
132+
// refresh anyway.
133+
// The next time we get new, valid credentials, HandleCredentialsChanged will kick off
134+
// a new Refresh
135+
_getInProgress=false;
136+
return;
137+
}
138+
139+
publicstringGetCachedSuffix()
140+
{
141+
usingvar_=_lock.Lock();
142+
return_domainSuffix;
143+
}
144+
}

‎App/ViewModels/TrayWindowViewModel.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public partial class TrayWindowViewModel : ObservableObject, IAgentExpanderHost
3535
privatereadonlyIRpcController_rpcController;
3636
privatereadonlyICredentialManager_credentialManager;
3737
privatereadonlyIAgentViewModelFactory_agentViewModelFactory;
38+
privatereadonlyIHostnameSuffixGetter_hostnameSuffixGetter;
3839

3940
privateFileSyncListWindow?_fileSyncListWindow;
4041

@@ -91,15 +92,14 @@ public partial class TrayWindowViewModel : ObservableObject, IAgentExpanderHost
9192

9293
[ObservableProperty]publicpartialstringDashboardUrl{get;set;}=DefaultDashboardUrl;
9394

94-
privatestring_hostnameSuffix=DefaultHostnameSuffix;
95-
9695
publicTrayWindowViewModel(IServiceProviderservices,IRpcControllerrpcController,
97-
ICredentialManagercredentialManager,IAgentViewModelFactoryagentViewModelFactory)
96+
ICredentialManagercredentialManager,IAgentViewModelFactoryagentViewModelFactory,IHostnameSuffixGetterhostnameSuffixGetter)
9897
{
9998
_services=services;
10099
_rpcController=rpcController;
101100
_credentialManager=credentialManager;
102101
_agentViewModelFactory=agentViewModelFactory;
102+
_hostnameSuffixGetter=hostnameSuffixGetter;
103103

104104
// Since the property value itself never changes, we add event
105105
// listeners for the underlying collection changing instead.
@@ -139,6 +139,9 @@ public void Initialize(DispatcherQueue dispatcherQueue)
139139

140140
_credentialManager.CredentialsChanged+=(_,credentialModel)=>UpdateFromCredentialModel(credentialModel);
141141
UpdateFromCredentialModel(_credentialManager.GetCachedCredentials());
142+
143+
_hostnameSuffixGetter.SuffixChanged+=(_,suffix)=>HandleHostnameSuffixChanged(suffix);
144+
HandleHostnameSuffixChanged(_hostnameSuffixGetter.GetCachedSuffix());
142145
}
143146

144147
privatevoidUpdateFromRpcModel(RpcModelrpcModel)
@@ -195,7 +198,7 @@ private void UpdateFromRpcModel(RpcModel rpcModel)
195198
this,
196199
uuid,
197200
fqdn,
198-
_hostnameSuffix,
201+
_hostnameSuffixGetter.GetCachedSuffix(),
199202
connectionStatus,
200203
credentialModel.CoderUrl,
201204
workspace?.Name));
@@ -214,7 +217,7 @@ private void UpdateFromRpcModel(RpcModel rpcModel)
214217
// Workspace ID is fine as a stand-in here, it shouldn't
215218
// conflict with any agent IDs.
216219
uuid,
217-
_hostnameSuffix,
220+
_hostnameSuffixGetter.GetCachedSuffix(),
218221
AgentConnectionStatus.Gray,
219222
credentialModel.CoderUrl,
220223
workspace.Name));
@@ -273,6 +276,22 @@ private void UpdateFromCredentialModel(CredentialModel credentialModel)
273276
DashboardUrl=credentialModel.CoderUrl?.ToString()??DefaultDashboardUrl;
274277
}
275278

279+
privatevoidHandleHostnameSuffixChanged(stringsuffix)
280+
{
281+
// Ensure we're on the UI thread.
282+
if(_dispatcherQueue==null)return;
283+
if(!_dispatcherQueue.HasThreadAccess)
284+
{
285+
_dispatcherQueue.TryEnqueue(()=>HandleHostnameSuffixChanged(suffix));
286+
return;
287+
}
288+
289+
foreach(varagentinAgents)
290+
{
291+
agent.ConfiguredHostnameSuffix=suffix;
292+
}
293+
}
294+
276295
publicvoidVpnSwitch_Toggled(objectsender,RoutedEventArgse)
277296
{
278297
if(senderis notToggleSwitchtoggleSwitch)return;

‎CoderSdk/Coder/CoderApiClient.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public partial interface ICoderApiClient
4949
publicvoidSetSessionToken(stringtoken);
5050
}
5151

52+
[JsonSerializable(typeof(AgentConnectionInfo))]
5253
[JsonSerializable(typeof(BuildInfo))]
5354
[JsonSerializable(typeof(Response))]
5455
[JsonSerializable(typeof(User))]

‎CoderSdk/Coder/WorkspaceAgents.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ namespace Coder.Desktop.CoderSdk.Coder;
33
publicpartialinterfaceICoderApiClient
44
{
55
publicTask<WorkspaceAgent>GetWorkspaceAgent(stringid,CancellationTokenct=default);
6+
publicTask<AgentConnectionInfo>GetAgentConnectionInfoGeneric(CancellationTokenct=default);
7+
}
8+
9+
publicclassAgentConnectionInfo
10+
{
11+
publicstringHostnameSuffix{get;set;}=string.Empty;
12+
// note that we're leaving out several fields including the DERP Map because
13+
// we don't use that information, and it's a complex object to define.
614
}
715

816
publicclassWorkspaceAgent
@@ -35,4 +43,9 @@ public Task<WorkspaceAgent> GetWorkspaceAgent(string id, CancellationToken ct =
3543
{
3644
returnSendRequestNoBodyAsync<WorkspaceAgent>(HttpMethod.Get,"/api/v2/workspaceagents/"+id,ct);
3745
}
46+
47+
publicTask<AgentConnectionInfo>GetAgentConnectionInfoGeneric(CancellationTokenct=default)
48+
{
49+
returnSendRequestNoBodyAsync<AgentConnectionInfo>(HttpMethod.Get,"/api/v2/workspaceagents/connection",ct);
50+
}
3851
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp