diff --git a/nomad/acl_endpoint.go b/nomad/acl_endpoint.go index 86622b94e..adc4bb554 100644 --- a/nomad/acl_endpoint.go +++ b/nomad/acl_endpoint.go @@ -1,6 +1,7 @@ package nomad import ( + "fmt" "time" metrics "github.com/armon/go-metrics" @@ -24,13 +25,26 @@ func (a *ACL) UpsertPolicy(args *structs.AllocListRequest, reply *structs.AllocL return nil } -// DeletePolicy is used to delete a policy -func (a *ACL) DeletePolicy(args *structs.AllocListRequest, reply *structs.AllocListResponse) error { - if done, err := a.srv.forward("ACL.DeletePolicy", args, args, reply); done { +// DeletePolicies is used to delete policies +func (a *ACL) DeletePolicies(args *structs.ACLPolicyDeleteRequest, reply *structs.GenericResponse) error { + if done, err := a.srv.forward("ACL.DeletePolicies", args, args, reply); done { return err } - defer metrics.MeasureSince([]string{"nomad", "acl", "delete_policy"}, time.Now()) - // TODO + defer metrics.MeasureSince([]string{"nomad", "acl", "delete_policies"}, time.Now()) + + // Validate non-zero set of policies + if len(args.Names) == 0 { + return fmt.Errorf("must specify as least one policy") + } + + // Update via Raft + _, index, err := a.srv.raftApply(structs.ACLPolicyDeleteRequestType, args) + if err != nil { + return err + } + + // Update the index + reply.Index = index return nil } diff --git a/nomad/acl_endpoint_test.go b/nomad/acl_endpoint_test.go index d8178e303..945a1a279 100644 --- a/nomad/acl_endpoint_test.go +++ b/nomad/acl_endpoint_test.go @@ -220,3 +220,26 @@ func TestACLEndpoint_List_Blocking(t *testing.T) { assert.Equal(t, uint64(3), resp2.Index) assert.Equal(t, 0, len(resp2.Policies)) } + +func TestACLEndpoint_DeletePolicy(t *testing.T) { + t.Parallel() + s1 := testServer(t, nil) + defer s1.Shutdown() + codec := rpcClient(t, s1) + testutil.WaitForLeader(t, s1.RPC) + + // Create the register request + p1 := mock.ACLPolicy() + s1.fsm.State().UpsertACLPolicies(1000, []*structs.ACLPolicy{p1}) + + // Lookup the policies + req := &structs.ACLPolicyDeleteRequest{ + Names: []string{p1.Name}, + WriteRequest: structs.WriteRequest{Region: "global"}, + } + var resp structs.GenericResponse + if err := msgpackrpc.CallWithCodec(codec, "ACL.DeletePolicies", req, &resp); err != nil { + t.Fatalf("err: %v", err) + } + assert.NotEqual(t, uint64(0), resp.Index) +} diff --git a/nomad/fsm.go b/nomad/fsm.go index 5f0d8bbbd..5feb56b27 100644 --- a/nomad/fsm.go +++ b/nomad/fsm.go @@ -168,6 +168,8 @@ func (n *nomadFSM) Apply(log *raft.Log) interface{} { return n.applyDeploymentDelete(buf[1:], log.Index) case structs.JobStabilityRequestType: return n.applyJobStability(buf[1:], log.Index) + case structs.ACLPolicyDeleteRequestType: + return n.applyACLPolicyDelete(buf[1:], log.Index) default: if ignoreUnknown { n.logger.Printf("[WARN] nomad.fsm: ignoring unknown message type (%d), upgrade to newer version", msgType) @@ -670,6 +672,21 @@ func (n *nomadFSM) applyJobStability(buf []byte, index uint64) interface{} { return nil } +// applyACLPolicyDelete is used to delete a set of policies +func (n *nomadFSM) applyACLPolicyDelete(buf []byte, index uint64) interface{} { + defer metrics.MeasureSince([]string{"nomad", "fsm", "apply_acl_policy_delete"}, time.Now()) + var req structs.ACLPolicyDeleteRequest + if err := structs.Decode(buf, &req); err != nil { + panic(fmt.Errorf("failed to decode request: %v", err)) + } + + if err := n.state.DeleteACLPolicies(index, req.Names); err != nil { + n.logger.Printf("[ERR] nomad.fsm: DeleteACLPolicies failed: %v", err) + return err + } + return nil +} + func (n *nomadFSM) Snapshot() (raft.FSMSnapshot, error) { // Create a new snapshot snap, err := n.state.Snapshot() diff --git a/nomad/fsm_test.go b/nomad/fsm_test.go index 0941fb1b2..b3103bb16 100644 --- a/nomad/fsm_test.go +++ b/nomad/fsm_test.go @@ -1518,6 +1518,34 @@ func TestFSM_DeleteDeployment(t *testing.T) { } } +func TestFSM_DeleteACLPolicies(t *testing.T) { + t.Parallel() + fsm := testFSM(t) + + policy := mock.ACLPolicy() + err := fsm.State().UpsertACLPolicies(1000, []*structs.ACLPolicy{policy}) + assert.Nil(t, err) + + req := structs.ACLPolicyDeleteRequest{ + Names: []string{policy.Name}, + } + buf, err := structs.Encode(structs.ACLPolicyDeleteRequestType, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + resp := fsm.Apply(makeLog(buf)) + if resp != nil { + t.Fatalf("resp: %v", resp) + } + + // Verify we are NOT registered + ws := memdb.NewWatchSet() + out, err := fsm.State().ACLPolicyByName(ws, policy.Name) + assert.Nil(t, err) + assert.Nil(t, out) +} + func testSnapshotRestore(t *testing.T, fsm *nomadFSM) *nomadFSM { // Snapshot snap, err := fsm.Snapshot() diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 1720e5a6e..2a79274c8 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -59,6 +59,8 @@ const ( DeploymentAllocHealthRequestType DeploymentDeleteRequestType JobStabilityRequestType + ACLPolicyUpsertRequestType + ACLPolicyDeleteRequestType ) const ( @@ -5371,3 +5373,9 @@ type SingleACLPolicyResponse struct { Policy *ACLPolicy QueryMeta } + +// ACLPolicyDeleteRequest is used to delete a set of policies +type ACLPolicyDeleteRequest struct { + Names []string + WriteRequest +}