Skip to content

Commit 53e440d

Browse files
authored
Move search tags to request options (#98)
1 parent 3d91d0e commit 53e440d

15 files changed

Lines changed: 270 additions & 193 deletions

cs/src/Management/ITunnelManagementClient.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace Microsoft.VsSaaS.TunnelService
1818
public interface ITunnelManagementClient : IDisposable
1919
{
2020
/// <summary>
21-
/// Lists all tunnels that are owned by the caller.
21+
/// Lists tunnels that are owned by the caller.
2222
/// </summary>
2323
/// <param name="clusterId">A tunnel cluster ID, or null to list tunnels globally.</param>
2424
/// <param name="domain">Tunnel domain, or null for the default domain.</param>
@@ -27,6 +27,11 @@ public interface ITunnelManagementClient : IDisposable
2727
/// <returns>Array of tunnel objects.</returns>
2828
/// <exception cref="UnauthorizedAccessException">The client access token was missing,
2929
/// invalid, or unauthorized.</exception>
30+
/// <remarks>
31+
/// The list can be filtered by setting <see cref="TunnelRequestOptions.Tags"/>.
32+
/// Ports will not be included in the returned tunnels unless
33+
/// <see cref="TunnelRequestOptions.IncludePorts"/> is set to true.
34+
/// </remarks>
3035
Task<Tunnel[]> ListTunnelsAsync(
3136
string? clusterId = null,
3237
string? domain = null,
@@ -45,6 +50,7 @@ Task<Tunnel[]> ListTunnelsAsync(
4550
/// <returns>Array of tunnel objects.</returns>
4651
/// <exception cref="UnauthorizedAccessException">The client access token was missing,
4752
/// invalid, or unauthorized.</exception>
53+
[Obsolete("Use ListTunnelsAsync() method with TunnelRequestOptions.Tags instead.")]
4854
Task<Tunnel[]> SearchTunnelsAsync(
4955
string[] tags,
5056
bool requireAllTags,
@@ -63,6 +69,10 @@ Task<Tunnel[]> SearchTunnelsAsync(
6369
/// <returns>The requested tunnel object, or null if the ID or name was not found.</returns>
6470
/// <exception cref="UnauthorizedAccessException">The client access token was missing,
6571
/// invalid, or unauthorized.</exception>
72+
/// <remarks>
73+
/// Ports will not be included in the returned tunnel unless
74+
/// <see cref="TunnelRequestOptions.IncludePorts"/> is set to true.
75+
/// </remarks>
6676
Task<Tunnel?> GetTunnelAsync(
6777
Tunnel tunnel,
6878
TunnelRequestOptions? options = null,
@@ -180,7 +190,7 @@ Task<bool> DeleteTunnelEndpointsAsync(
180190
CancellationToken cancellation = default);
181191

182192
/// <summary>
183-
/// Lists all ports on a tunnel.
193+
/// Lists ports on a tunnel.
184194
/// </summary>
185195
/// <param name="tunnel">Tunnel object including at least either a tunnel name
186196
/// (globally unique, if configured) or tunnel ID and cluster ID.</param>
@@ -191,6 +201,9 @@ Task<bool> DeleteTunnelEndpointsAsync(
191201
/// invalid, or unauthorized.</exception>
192202
/// <exception cref="InvalidOperationException">The tunnel ID or name was not found.
193203
/// </exception>
204+
/// <remarks>
205+
/// The list can be filtered by setting <see cref="TunnelRequestOptions.Tags"/>.
206+
/// </remarks>
194207
Task<TunnelPort[]> ListTunnelPortsAsync(
195208
Tunnel tunnel,
196209
TunnelRequestOptions? options = null,

cs/src/Management/TunnelManagementClient.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,7 @@ public async Task<Tunnel[]> ListTunnelsAsync(
599599
}
600600

601601
/// <inheritdoc />
602+
[Obsolete("Use ListTunnelsAsync() method with TunnelRequestOptions.Tags instead.")]
602603
public async Task<Tunnel[]> SearchTunnelsAsync(
603604
string[] tags,
604605
bool requireAllTags,

cs/src/Management/TunnelRequestOptions.cs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ namespace Microsoft.VsSaaS.TunnelService
2222
/// </remarks>
2323
public class TunnelRequestOptions
2424
{
25+
private static readonly string[] TrueOption = new[] { "true" };
26+
2527
/// <summary>
2628
/// Gets or sets a tunnel access token for the request.
2729
/// </summary>
@@ -55,10 +57,29 @@ public class TunnelRequestOptions
5557
public bool FollowRedirects { get; set; } = true;
5658

5759
/// <summary>
58-
/// Gets or sets a flag that requests tunnel ports when retrieving a tunnel object.
60+
/// Gets or sets a flag that requests tunnel ports when retrieving tunnels.
5961
/// </summary>
6062
public bool IncludePorts { get; set; }
6163

64+
/// <summary>
65+
/// Gets or sets an optional list of tags to filter the requested tunnels or ports.
66+
/// </summary>
67+
/// <remarks>
68+
/// Requested tags are compared to the <see cref="Tunnel.Tags"/> or
69+
/// <see cref="TunnelPort.Tags"/> when calling
70+
/// <see cref="ITunnelManagementClient.ListTunnelsAsync"/> or
71+
/// <see cref="ITunnelManagementClient.ListTunnelPortsAsync"/> respectively. By default, an
72+
/// item is included if ANY tag matches; set <see cref="RequireAllTags" /> to match ALL
73+
/// tags instead.
74+
/// </remarks>
75+
public string[]? Tags { get; set; }
76+
77+
/// <summary>
78+
/// Gets or sets a flag that indicates whether listed items must match all tags
79+
/// specified in <see cref="Tags"/>. If false, an item is included if any tag matches.
80+
/// </summary>
81+
public bool RequireAllTags { get; set; }
82+
6283
/// <summary>
6384
/// Gets or sets an optional list of scopes that should be authorized when retrieving a
6485
/// tunnel or tunnel port object.
@@ -89,32 +110,42 @@ public class TunnelRequestOptions
89110
/// </summary>
90111
protected internal virtual string ToQueryString()
91112
{
92-
var queryOptions = new Dictionary<string, string>();
113+
var queryOptions = new Dictionary<string, string[]>();
93114

94115
if (IncludePorts)
95116
{
96-
queryOptions["includePorts"] = "true";
117+
queryOptions["includePorts"] = TrueOption;
97118
}
98119

99120
if (Scopes != null)
100121
{
101122
TunnelAccessControl.ValidateScopes(Scopes);
102-
queryOptions["scopes"] = string.Join(",", Scopes);
123+
queryOptions["scopes"] = Scopes;
103124
}
104125

105126
if (TokenScopes != null)
106127
{
107128
TunnelAccessControl.ValidateScopes(TokenScopes);
108-
queryOptions["tokenScopes"] = string.Join(",", TokenScopes);
129+
queryOptions["tokenScopes"] = TokenScopes;
109130
}
110131

111132
if (ForceRename)
112133
{
113-
queryOptions["forceRename"] = "true";
134+
queryOptions["forceRename"] = TrueOption;
135+
}
136+
137+
if (Tags != null && Tags.Length > 0)
138+
{
139+
queryOptions["tags"] = Tags;
140+
if (RequireAllTags)
141+
{
142+
queryOptions["allTags"] = TrueOption;
143+
}
114144
}
115145

146+
// Note the comma separator for multi-valued options is NOT URL-encoded.
116147
return string.Join('&', queryOptions.Select(
117-
(o) => $"{o.Key}={HttpUtility.UrlEncode(o.Value)}"));
148+
(o) => $"{o.Key}={string.Join(",", o.Value.Select(HttpUtility.UrlEncode))}"));
118149
}
119150
}
120151
}

go/tunnels/manager.go

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"net/http"
1414
"net/url"
1515
"reflect"
16-
"strconv"
1716
"strings"
1817
)
1918

@@ -112,7 +111,7 @@ func NewManager(userAgents []UserAgent, tp tokenProviderfn, tunnelServiceUrl *ur
112111
return &Manager{tokenProvider: tp, httpClient: client, uri: tunnelServiceUrl, userAgents: userAgents}, nil
113112
}
114113

115-
// Lists all tunnels owned by the authenticated user.
114+
// Lists tunnels owned by the authenticated user.
116115
// Returns a list of tunnels or an error if the search fails.
117116
func (m *Manager) ListTunnels(
118117
ctx context.Context, clusterID string, domain string, options *TunnelRequestOptions,
@@ -138,37 +137,6 @@ func (m *Manager) ListTunnels(
138137
return ts, nil
139138
}
140139

141-
// Search tunnels that the authenticated user has access to based on tags.
142-
// If requireAllTags is true then tunnels returned must contain all tags in the tags slice.
143-
// Returns a slice of the found tunnels or an error if the search fails.
144-
func (m *Manager) SearchTunnels(
145-
ctx context.Context, tags []string, requireAllTags bool, clusterID string, domain string, options *TunnelRequestOptions,
146-
) (ts []*Tunnel, err error) {
147-
queryParams := url.Values{}
148-
if clusterID == "" {
149-
queryParams.Add("global", "true")
150-
}
151-
if domain != "" {
152-
queryParams.Add("domain", domain)
153-
}
154-
queryParams.Add("allTags", strconv.FormatBool(requireAllTags))
155-
tagString := strings.Join(tags, ",")
156-
queryParams.Add("tags", tagString)
157-
158-
url := m.buildUri(clusterID, tunnelsApiPath, options, queryParams.Encode())
159-
response, err := m.sendTunnelRequest(ctx, nil, options, http.MethodGet, url, nil, nil, readAccessTokenScope, false)
160-
if err != nil {
161-
return nil, fmt.Errorf("error sending search tunnel request: %w", err)
162-
}
163-
164-
err = json.Unmarshal(response, &ts)
165-
if err != nil {
166-
return nil, fmt.Errorf("error parsing response json to tunnel: %w", err)
167-
}
168-
169-
return ts, nil
170-
}
171-
172140
// Gets a tunnel by id or name.
173141
// If getting a tunnel by name the domain must be provided if the tunnel is not in the default domain.
174142
// Returns the requested tunnel or an error if the tunnel is not found.
@@ -338,7 +306,7 @@ func (m *Manager) DeleteTunnelEndpoints(
338306
return err
339307
}
340308

341-
// Lists all ports on the tunnel.
309+
// Lists ports on the tunnel.
342310
func (m *Manager) ListTunnelPorts(
343311
ctx context.Context, tunnel *Tunnel, options *TunnelRequestOptions,
344312
) (tp []*TunnelPort, err error) {

go/tunnels/request_options.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ type TunnelRequestOptions struct {
2121
// Flag that requests tunnel ports when retrieving a tunnel object.
2222
IncludePorts bool
2323

24+
// Optional list of tags to filter the requested tunnels or ports.
25+
// By default, an item is included if ANY tag matches; set `requireAllTags` to match
26+
// ALL tags instead.
27+
Tags []string
28+
29+
// Flag that indicates whether listed items must match all tags specified in `tags`.
30+
// If false, an item is included if any tag matches.
31+
RequireAllTags bool
32+
2433
// List of scopes that are needed for the current request.
2534
Scopes TunnelAccessScopes
2635

@@ -54,6 +63,15 @@ func (options *TunnelRequestOptions) queryString() string {
5463
if options.ForceRename {
5564
queryOptions.Set("forceRename", "true")
5665
}
66+
if options.Tags != nil {
67+
for _, tag := range options.Tags {
68+
queryOptions.Add("tags", string(tag))
69+
}
70+
71+
if options.RequireAllTags {
72+
queryOptions.Set("allTags", "true")
73+
}
74+
}
5775

5876
return queryOptions.Encode()
5977
}

java/src/main/java/com/microsoft/tunnels/management/ITunnelManagementClient.java

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,14 @@
1717
*/
1818
public interface ITunnelManagementClient {
1919
/**
20-
* Lists all tunnels that are owned by the caller.
20+
* Lists tunnels that are owned by the caller.
2121
*
2222
* @param clusterId A tunnel cluster ID, or null to list tunnels globally.
23+
* @param domain Tunnel domain, or null for the default domain.
2324
* @param options Request options.
2425
* @return Array of tunnel objects.
2526
*/
2627
public CompletableFuture<Collection<Tunnel>> listTunnelsAsync(
27-
String clusterId,
28-
TunnelRequestOptions options);
29-
30-
/**
31-
* Search for all tunnels with matching tags.
32-
*
33-
* @param tags The tags to search for.
34-
* @param requireAllTags requires a tunnel to have all specified tags.
35-
* @param clusterId A tunnel cluster ID, or null to list tunnels globally.
36-
* @param domain Tunnel domain, or null for the default domain.
37-
* @param options Request options.
38-
* @return Array of tunnel objects.
39-
*/
40-
public CompletableFuture<Collection<Tunnel>> searchTunnelsAsync(
41-
String[] tags,
42-
boolean requireAllTags,
4328
String clusterId,
4429
String domain,
4530
TunnelRequestOptions options);

java/src/main/java/com/microsoft/tunnels/management/TunnelManagementClient.java

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,16 @@
66
import com.google.gson.Gson;
77
import com.google.gson.reflect.TypeToken;
88
import com.microsoft.tunnels.contracts.Tunnel;
9-
import com.microsoft.tunnels.contracts.TunnelAccessControl;
109
import com.microsoft.tunnels.contracts.TunnelAccessControlEntry;
1110
import com.microsoft.tunnels.contracts.TunnelAccessScopes;
1211
import com.microsoft.tunnels.contracts.TunnelConnectionMode;
1312
import com.microsoft.tunnels.contracts.TunnelContracts;
1413
import com.microsoft.tunnels.contracts.TunnelEndpoint;
1514
import com.microsoft.tunnels.contracts.TunnelPort;
1615

17-
import java.io.UnsupportedEncodingException;
1816
import java.lang.reflect.Type;
1917
import java.net.URI;
2018
import java.net.URISyntaxException;
21-
import java.net.URLEncoder;
2219
import java.net.http.HttpClient;
2320
import java.net.http.HttpRequest;
2421
import java.net.http.HttpRequest.BodyPublishers;
@@ -242,7 +239,7 @@ private URI buildUri(String clusterId,
242239

243240
String queryString = "";
244241
if (options != null) {
245-
queryString = toQueryString(options);
242+
queryString = options.toQueryString();
246243
}
247244
if (query != null) {
248245
queryString += StringUtils.isBlank(queryString) ? query : "&" + query;
@@ -263,38 +260,10 @@ private URI buildUri(String clusterId,
263260
}
264261
}
265262

266-
private String toQueryString(TunnelRequestOptions options) {
267-
final String encoding = "UTF-8";
268-
var queryOptions = new ArrayList<String>();
269-
if (options.includePorts) {
270-
queryOptions.add("includePorts=true");
271-
}
272-
273-
if (options.scopes != null) {
274-
TunnelAccessControl.validateScopes(options.scopes, null);
275-
try {
276-
queryOptions.add("scopes=" + URLEncoder.encode(String.join(",", options.scopes), encoding));
277-
} catch (UnsupportedEncodingException e) {
278-
throw new IllegalArgumentException("Bad encoding: " + encoding);
279-
}
280-
}
281-
282-
if (options.tokenScopes != null) {
283-
TunnelAccessControl.validateScopes(options.tokenScopes, null);
284-
try {
285-
queryOptions.add(
286-
"tokenScopes=" + URLEncoder.encode(String.join(",", options.tokenScopes), encoding));
287-
} catch (UnsupportedEncodingException e) {
288-
throw new IllegalArgumentException("Bad encoding: " + encoding);
289-
}
290-
}
291-
return String.join("&", queryOptions);
292-
}
293-
294263
public CompletableFuture<Collection<Tunnel>> listTunnelsAsync(
295264
String clusterId,
265+
String domain,
296266
TunnelRequestOptions options) {
297-
// TODO - add domain parameter
298267
var query = StringUtils.isBlank(clusterId) ? "global=true" : null;
299268
var requestUri = this.buildUri(clusterId, tunnelsApiPath, options, query);
300269
final Type responseType = new TypeToken<Collection<Tunnel>>() {
@@ -309,17 +278,6 @@ public CompletableFuture<Collection<Tunnel>> listTunnelsAsync(
309278
responseType);
310279
}
311280

312-
@Override
313-
public CompletableFuture<Collection<Tunnel>> searchTunnelsAsync(
314-
String[] tags,
315-
boolean requireAllTags,
316-
String clusterId,
317-
String domain,
318-
TunnelRequestOptions options) {
319-
// TODO Auto-generated method stub
320-
return null;
321-
}
322-
323281
@Override
324282
public CompletableFuture<Tunnel> getTunnelAsync(Tunnel tunnel, TunnelRequestOptions options) {
325283
var requestUri = buildUri(tunnel, options, null, null);

0 commit comments

Comments
 (0)