diff --git a/consul/acl.go b/consul/acl.go index b35ace94f5..95d76bec32 100644 --- a/consul/acl.go +++ b/consul/acl.go @@ -69,7 +69,7 @@ func (s *Server) lookupACL(id, authDC string) (acl.ACL, error) { } // Attempt to refresh the policy - args := structs.ACLSpecificRequest{ + args := structs.ACLPolicyRequest{ Datacenter: authDC, ACL: id, } diff --git a/consul/acl_endpoint.go b/consul/acl_endpoint.go index eb6d4b20aa..8761426331 100644 --- a/consul/acl_endpoint.go +++ b/consul/acl_endpoint.go @@ -88,7 +88,7 @@ func (a *ACL) Get(args *structs.ACLSpecificRequest, // GetPolicy is used to retrieve a compiled policy object with a TTL. Does not // support a blocking query. -func (a *ACL) GetPolicy(args *structs.ACLSpecificRequest, reply *structs.ACLPolicy) error { +func (a *ACL) GetPolicy(args *structs.ACLPolicyRequest, reply *structs.ACLPolicy) error { if done, err := a.srv.forward("ACL.GetPolicy", args, args, reply); done { return err } @@ -99,12 +99,20 @@ func (a *ACL) GetPolicy(args *structs.ACLSpecificRequest, reply *structs.ACLPoli return err } - // Setup the response + // Generate an ETag conf := a.srv.config - reply.Policy = policy + etag := fmt.Sprintf("%s:%s", conf.ACLDefaultPolicy, policy.ID) + + // Setup the response + reply.ETag = etag reply.Root = conf.ACLDefaultPolicy reply.TTL = conf.ACLTTL a.srv.setQueryMeta(&reply.QueryMeta) + + // Only send the policy on an Etag mis-match + if args.ETag != etag { + reply.Policy = policy + } return nil } diff --git a/consul/acl_endpoint_test.go b/consul/acl_endpoint_test.go index 032df55206..f3daceccc9 100644 --- a/consul/acl_endpoint_test.go +++ b/consul/acl_endpoint_test.go @@ -130,7 +130,7 @@ func TestACLEndpoint_GetPolicy(t *testing.T) { t.Fatalf("err: %v", err) } - getR := structs.ACLSpecificRequest{ + getR := structs.ACLPolicyRequest{ Datacenter: "dc1", ACL: out, } @@ -145,6 +145,20 @@ func TestACLEndpoint_GetPolicy(t *testing.T) { if acls.TTL != 30*time.Second { t.Fatalf("bad: %v", acls) } + + // Do a conditional lookup with etag + getR.ETag = acls.ETag + var out2 structs.ACLPolicy + if err := client.Call("ACL.GetPolicy", &getR, &out2); err != nil { + t.Fatalf("err: %v", err) + } + + if out2.Policy != nil { + t.Fatalf("Bad: %v", out2) + } + if out2.TTL != 30*time.Second { + t.Fatalf("bad: %v", out2) + } } func TestACLEndpoint_List(t *testing.T) { diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 317af5ee32..b80145c436 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -466,12 +466,26 @@ func (r *ACLSpecificRequest) RequestDatacenter() string { return r.Datacenter } +// ACLPolicyRequest is used to request an ACL by ID, conditionally +// filtering on an ID +type ACLPolicyRequest struct { + Datacenter string + ACL string + ETag string + QueryOptions +} + +func (r *ACLPolicyRequest) RequestDatacenter() string { + return r.Datacenter +} + type IndexedACLs struct { ACLs ACLs QueryMeta } type ACLPolicy struct { + ETag string Root string Policy *acl.Policy TTL time.Duration