Skip to content

Commit b9e1df8

Browse files
committed
policyfile: add SetAndGet method
This variant of Set returns the resulting ACL with its updated ETag. Updates tailscale/corp#22748 Signed-off-by: Percy Wegmann <percy@tailscale.com>
1 parent 1ce94fb commit b9e1df8

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

policyfile.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,32 @@ func (pr *PolicyFileResource) Set(ctx context.Context, acl any, etag string) err
221221
return pr.do(req, nil)
222222
}
223223

224+
// SetAndGet sets the [ACL] for the tailnet and returns the resulting [ACL].
225+
// etag is an optional value that, if supplied, will be used in the "If-Match" HTTP request header.
226+
func (pr *PolicyFileResource) SetAndGet(ctx context.Context, acl ACL, etag string) (*ACL, error) {
227+
headers := make(map[string]string)
228+
if etag != "" {
229+
headers["If-Match"] = fmt.Sprintf("%q", etag)
230+
}
231+
232+
reqOpts := []requestOption{
233+
requestHeaders(headers),
234+
requestBody(acl),
235+
}
236+
237+
req, err := pr.buildRequest(ctx, http.MethodPost, pr.buildTailnetURL("acl"), reqOpts...)
238+
if err != nil {
239+
return nil, err
240+
}
241+
242+
out, header, err := bodyWithResponseHeader[ACL](pr, req)
243+
if err != nil {
244+
return nil, err
245+
}
246+
out.ETag = header.Get("Etag")
247+
return out, nil
248+
}
249+
224250
// Validate validates the provided ACL via the API. acl can either be an [ACL], or a HuJSON string.
225251
func (pr *PolicyFileResource) Validate(ctx context.Context, acl any) error {
226252
reqOpts := []requestOption{

policyfile_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,62 @@ func TestClient_SetACL(t *testing.T) {
283283
assert.EqualValues(t, expectedACL, actualACL)
284284
}
285285

286+
func TestClient_SetAndGetACL(t *testing.T) {
287+
t.Parallel()
288+
289+
client, server := NewTestHarness(t)
290+
server.ResponseCode = http.StatusOK
291+
server.ResponseHeader.Set("ETag", "abcdefg")
292+
in := ACL{
293+
ACLs: []ACLEntry{
294+
{
295+
Action: "accept",
296+
Ports: []string{"*:*"},
297+
Users: []string{"*"},
298+
},
299+
},
300+
TagOwners: map[string][]string{
301+
"tag:example": {"group:example"},
302+
},
303+
Hosts: map[string]string{
304+
"example-host-1": "100.100.100.100",
305+
"example-host-2": "100.100.101.100/24",
306+
},
307+
Groups: map[string][]string{
308+
"group:example": {
309+
"user1@example.com",
310+
"user2@example.com",
311+
},
312+
},
313+
Tests: []ACLTest{
314+
{
315+
User: "user1@example.com",
316+
Allow: []string{"example-host-1:22", "example-host-2:80"},
317+
Deny: []string{"exapmle-host-2:100"},
318+
},
319+
{
320+
User: "user2@example.com",
321+
Allow: []string{"100.60.3.4:22"},
322+
},
323+
},
324+
ETag: "abcdefg",
325+
}
326+
server.ResponseBody = in
327+
328+
out, err := client.PolicyFile().SetAndGet(context.Background(), in, "abcdefg")
329+
assert.NoError(t, err)
330+
assert.Equal(t, http.MethodPost, server.Method)
331+
assert.Equal(t, "/api/v2/tailnet/example.com/acl", server.Path)
332+
assert.Equal(t, `"abcdefg"`, server.Header.Get("If-Match"))
333+
assert.EqualValues(t, "application/json", server.Header.Get("Content-Type"))
334+
assert.EqualValues(t, &in, out)
335+
336+
var actualACL ACL
337+
assert.NoError(t, json.Unmarshal(server.Body.Bytes(), &actualACL))
338+
in.ETag = ""
339+
assert.EqualValues(t, in, actualACL)
340+
}
341+
286342
func TestClient_SetACL_HuJSON(t *testing.T) {
287343
t.Parallel()
288344

0 commit comments

Comments
 (0)