// RemoveSecret removes the secret referenced by `RemoveSecretRequest.ID`. // - Returns `InvalidArgument` if `RemoveSecretRequest.ID` is empty. // - Returns `NotFound` if the a secret named `RemoveSecretRequest.ID` is not found. // - Returns `SecretInUse` if the secret is currently in use // - Returns an error if the deletion fails. func (s *Server) RemoveSecret(ctx context.Context, request *api.RemoveSecretRequest) (*api.RemoveSecretResponse, error) { if request.SecretID == "" { return nil, grpc.Errorf(codes.InvalidArgument, "secret ID must be provided") } err := s.store.Update(func(tx store.Tx) error { // Check if the secret exists secret := store.GetSecret(tx, request.SecretID) if secret == nil { return grpc.Errorf(codes.NotFound, "could not find secret %s", request.SecretID) } // Check if any services currently reference this secret, return error if so services, err := store.FindServices(tx, store.ByReferencedSecretID(request.SecretID)) if err != nil { return grpc.Errorf(codes.Internal, "could not find services using secret %s: %v", request.SecretID, err) } if len(services) != 0 { serviceNames := make([]string, 0, len(services)) for _, service := range services { serviceNames = append(serviceNames, service.Spec.Annotations.Name) } secretName := secret.Spec.Annotations.Name serviceNameStr := strings.Join(serviceNames, ", ") serviceStr := "services" if len(serviceNames) == 1 { serviceStr = "service" } return grpc.Errorf(codes.InvalidArgument, "secret '%s' is in use by the following %s: %v", secretName, serviceStr, serviceNameStr) } return store.DeleteSecret(tx, request.SecretID) }) switch err { case store.ErrNotExist: return nil, grpc.Errorf(codes.NotFound, "secret %s not found", request.SecretID) case nil: log.G(ctx).WithFields(logrus.Fields{ "secret.ID": request.SecretID, "method": "RemoveSecret", }).Debugf("secret removed") return &api.RemoveSecretResponse{}, nil default: return nil, err } }
// RemoveSecret removes the secret referenced by `RemoveSecretRequest.ID`. // - Returns `InvalidArgument` if `RemoveSecretRequest.ID` is empty. // - Returns `NotFound` if the a secret named `RemoveSecretRequest.ID` is not found. // - Returns an error if the deletion fails. func (s *Server) RemoveSecret(ctx context.Context, request *api.RemoveSecretRequest) (*api.RemoveSecretResponse, error) { if request.SecretID == "" { return nil, grpc.Errorf(codes.InvalidArgument, "secret ID must be provided") } err := s.store.Update(func(tx store.Tx) error { return store.DeleteSecret(tx, request.SecretID) }) switch err { case store.ErrNotExist: return nil, grpc.Errorf(codes.NotFound, "secret %s not found", request.SecretID) case nil: return &api.RemoveSecretResponse{}, nil default: return nil, err } }
// RemoveSecret removes the secret referenced by `RemoveSecretRequest.ID`. // - Returns `InvalidArgument` if `RemoveSecretRequest.ID` is empty. // - Returns `NotFound` if the a secret named `RemoveSecretRequest.ID` is not found. // - Returns an error if the deletion fails. func (s *Server) RemoveSecret(ctx context.Context, request *api.RemoveSecretRequest) (*api.RemoveSecretResponse, error) { if request.SecretID == "" { return nil, grpc.Errorf(codes.InvalidArgument, "secret ID must be provided") } err := s.store.Update(func(tx store.Tx) error { return store.DeleteSecret(tx, request.SecretID) }) switch err { case store.ErrNotExist: return nil, grpc.Errorf(codes.NotFound, "secret %s not found", request.SecretID) case nil: log.G(ctx).WithFields(logrus.Fields{ "secret.ID": request.SecretID, "method": "RemoveSecret", }).Debugf("secret removed") return &api.RemoveSecretResponse{}, nil default: return nil, err } }
// If a secret is updated or deleted, even if it's for an existing task, no changes will be sent down func TestAssignmentsSecretUpdateAndDeletion(t *testing.T) { t.Parallel() gd, err := startDispatcher(DefaultConfig()) assert.NoError(t, err) defer gd.Close() expectedSessionID, nodeID := getSessionAndNodeID(t, gd.Clients[0]) // create the relevant secrets and tasks secrets, tasks := makeTasksAndSecrets(t, nodeID) err = gd.Store.Update(func(tx store.Tx) error { for _, secret := range secrets[:len(secrets)-1] { assert.NoError(t, store.CreateSecret(tx, secret)) } for _, task := range tasks { assert.NoError(t, store.CreateTask(tx, task)) } return nil }) assert.NoError(t, err) stream, err := gd.Clients[0].Assignments(context.Background(), &api.AssignmentsRequest{SessionID: expectedSessionID}) assert.NoError(t, err) defer stream.CloseSend() time.Sleep(100 * time.Millisecond) // check the initial task and secret stream resp, err := stream.Recv() assert.NoError(t, err) // FIXME(aaronl): This is hard to maintain. assert.Equal(t, 16, len(resp.Changes)) taskChanges, secretChanges := collectTasksAndSecrets(resp.Changes) assert.Len(t, taskChanges, 10) // 10 types of task states >= assigned, 2 types < assigned for _, task := range tasks[2:] { assert.NotNil(t, taskChanges[idAndAction{id: task.ID, action: api.AssignmentChange_AssignmentActionUpdate}]) } assert.Len(t, secretChanges, 6) // 6 types of task states between assigned and running inclusive for _, secret := range secrets[2:8] { assert.NotNil(t, secretChanges[idAndAction{id: secret.ID, action: api.AssignmentChange_AssignmentActionUpdate}]) } // updating secrets, used by tasks or not, do not cause any changes err = gd.Store.Update(func(tx store.Tx) error { for _, secret := range secrets[:len(secrets)-2] { secret.Spec.Data = []byte("new secret data") assert.NoError(t, store.UpdateSecret(tx, secret)) } return nil }) assert.NoError(t, err) recvChan := make(chan struct{}) go func() { _, _ = stream.Recv() recvChan <- struct{}{} }() select { case <-recvChan: assert.Fail(t, "secret update should not trigger dispatcher update") case <-time.After(250 * time.Millisecond): } // deleting secrets, used by tasks or not, do not cause any changes err = gd.Store.Update(func(tx store.Tx) error { for _, secret := range secrets[:len(secrets)-2] { assert.NoError(t, store.DeleteSecret(tx, secret.ID)) } return nil }) assert.NoError(t, err) select { case <-recvChan: assert.Fail(t, "secret delete should not trigger dispatcher update") case <-time.After(250 * time.Millisecond): } }