diff --git a/consul/acl_endpoint.go b/consul/acl_endpoint.go index 6a7cfc472b..f3c162b989 100644 --- a/consul/acl_endpoint.go +++ b/consul/acl_endpoint.go @@ -55,22 +55,12 @@ func (a *ACL) Apply(args *structs.ACLRequest, reply *string) error { return fmt.Errorf("ACL rule compilation failed: %v", err) } - // Check if this is an update - state := a.srv.fsm.State() - var existing *structs.ACL - if args.ACL.ID != "" { - _, existing, err = state.ACLGet(args.ACL.ID) - if err != nil { - a.srv.logger.Printf("[ERR] consul.acl: ACL lookup failed: %v", err) - return err - } - } - - // If this is a create, generate a new ID. This must + // If no ID is provided, generate a new ID. This must // be done prior to appending to the raft log, because the ID is not // deterministic. Once the entry is in the log, the state update MUST // be deterministic or the followers will not converge. - if existing == nil { + if args.ACL.ID == "" { + state := a.srv.fsm.State() for { args.ACL.ID = generateUUID() _, acl, err := state.ACLGet(args.ACL.ID) diff --git a/consul/acl_endpoint_test.go b/consul/acl_endpoint_test.go index 4c2e4410a3..11a305a949 100644 --- a/consul/acl_endpoint_test.go +++ b/consul/acl_endpoint_test.go @@ -148,6 +148,53 @@ func TestACLEndpoint_Update_PurgeCache(t *testing.T) { } } +func TestACLEndpoint_Apply_CustomID(t *testing.T) { + dir1, s1 := testServerWithConfig(t, func(c *Config) { + c.ACLDatacenter = "dc1" + c.ACLMasterToken = "root" + }) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + client := rpcClient(t, s1) + defer client.Close() + + testutil.WaitForLeader(t, client.Call, "dc1") + + arg := structs.ACLRequest{ + Datacenter: "dc1", + Op: structs.ACLSet, + ACL: structs.ACL{ + ID: "foobarbaz", // Specify custom ID, does not exist + Name: "User token", + Type: structs.ACLTypeClient, + }, + WriteRequest: structs.WriteRequest{Token: "root"}, + } + var out string + if err := client.Call("ACL.Apply", &arg, &out); err != nil { + t.Fatalf("err: %v", err) + } + if out != "foobarbaz" { + t.Fatalf("bad token ID: %s", out) + } + + // Verify + state := s1.fsm.State() + _, s, err := state.ACLGet(out) + if err != nil { + t.Fatalf("err: %v", err) + } + if s == nil { + t.Fatalf("should not be nil") + } + if s.ID != out { + t.Fatalf("bad: %v", s) + } + if s.Name != "User token" { + t.Fatalf("bad: %v", s) + } +} + func TestACLEndpoint_Apply_Denied(t *testing.T) { dir1, s1 := testServerWithConfig(t, func(c *Config) { c.ACLDatacenter = "dc1"