]> git.eshelyaron.com Git - emacs.git/commitdiff
Slightly simplify 'kubed-define-resource'
authorEshel Yaron <me@eshelyaron.com>
Mon, 22 Jul 2024 21:10:08 +0000 (23:10 +0200)
committerEshel Yaron <me@eshelyaron.com>
Tue, 23 Jul 2024 10:16:06 +0000 (12:16 +0200)
lisp/net/kubed.el

index 785c2aecfd942e94c90ecbb93e3521d40b78582a..64a887076cdd2c797a7ab5d3fdee418c96662cac 100644 (file)
@@ -25,8 +25,9 @@
 ;;
 ;; Use `kubed-display-pod' to display a Kuberenetes pod,
 ;; `kubed-edit-pod' to edit it, `kubed-delete-pods' to delete it, and
-;; `kubed-list-pods' to see a menu of all pods.  To update the list of
-;; current pods, use `kubed-update-pods' or `kubed-update-all'.
+;; `kubed-list-pods' to see a menu of all pods.  You can create new pods
+;; from YAML or JSON files with `kubed-create-pod'.  To update the list
+;; of current pods, use `kubed-update-pods' or `kubed-update-all'.
 ;;
 ;; Similar commands are defined for other types of resources as well.
 ;;
 ;; current `kubectl' context and namespace.  To change your current
 ;; Kuberenetes context or namespace, use `kubed-use-context' and
 ;; `kubed-set-namespace'; all resource lists are updated automatically
-;; after you do so.
+;; after you do so.  In addition, you can use the minor mode
+;; `kubed-all-namespaces-mode' to see resources from all namespaces.
 ;;
 ;; If you want to work with more or different types of Kubernetes
 ;; resources, use the macro `kubed-define-resource'.  This macro defines
 ;; some common functions and commands that'll get you started with ease.
 
-;;; TODO:
-
-;; - Add a way to filter resources lists.
-;; - Add `kubed-create-*' commands for more resource types.
-
 ;;; Code:
 
 (defgroup kubed nil
@@ -94,9 +91,9 @@ obtaining new information from Kuberenetes clusters.")
   "Show Kubernetes resources from all namespaces, not just current namespace."
   :global t
   (message "Kubed \"all namespaces\" mode is now %s"
-           (if kubed-all-namespaces-mode "ON" "OFF"))
-  (kubed-update-all))
+           (if kubed-all-namespaces-mode "ON" "OFF")))
 
+;;;###autoload
 (defmacro kubed-define-resource (resource &optional properties &rest commands)
   "Define Kubernetes RESOURCE with associated PROPERTIES and COMMANDS.
 
@@ -112,10 +109,11 @@ interacting with Kubernetes RESOURCEs:
   current namespace.  The RESOURCEs list buffer uses a dedicated major
   mode, `kubed-RESOURCEs-mode', which is also defined by this macro.
 - `kubed-update-RESROURCEs': update and repopulate RESOURCEs list.
+- `kubed-create-RESROURCE': create a RESOURCE from a YAML or a JSON file.
 
 PROPERTIES is a list of elements (PROPERTY JSON-PATH WIDTH SORT . ATTRS)
 that specify properties of RESOURCEs.  PROPERTY is the name of the
-property, as a symbol; JSON-PATH is JSONPath expression that evaluates
+property, as a symbol; JSON-PATH is JSONPath expression that evaluates
 to the value of PROPERTY when applied to the full JSON representation of
 a RESOURCE.  WIDTH, SORT and ATTRS are optional and can be omitted.
 WIDTH is used as the default width of the column corresponding to
@@ -176,14 +174,9 @@ namespaceless resource type, put `:namespaced nil' before COMMANDS:
         (affx-fun (intern (format "kubed-%Ss-affixation" resource)))
         (updt-cmd (intern (format "kubed-update-%Ss"        resource)))
         (list-cmd (intern (format "kubed-list-%Ss"          resource)))
-        (edit-cmd (intern (format "kubed-%Ss-edit"          resource)))
-        (slct-cmd (intern (format "kubed-%Ss-get"           resource)))
-        (othr-cmd (intern (format "kubed-%Ss-get-in-other-window" resource)))
-        (desc-cmd (intern (format "kubed-%Ss-display"       resource)))
         (mark-cmd (intern (format "kubed-%Ss-mark-for-deletion" resource)))
         (umrk-cmd (intern (format "kubed-%Ss-unmark"        resource)))
         (exec-cmd (intern (format "kubed-%Ss-execute"       resource)))
-        (dlt-cmd  (intern (format "kubed-%Ss-delete"        resource)))
         (list-buf         (format "*kubed-%Ss*"             resource))
         (buf-name         (format "*kubed-%S*"              resource))
         (out-name         (format " *kubed-get-%Ss*"        resource))
@@ -193,10 +186,65 @@ namespaceless resource type, put `:namespaced nil' before COMMANDS:
         (edt-name (intern (format "kubed-edit-%S"           resource)))
         (dlt-name (intern (format "kubed-delete-%Ss"        resource)))
         (mod-name (intern (format "kubed-%Ss-mode"          resource)))
-        (namespaced t))
-    (when (eq (car commands) :namespaced)
-      (pop commands)
-      (setq namespaced (pop commands)))
+        (crt-name (intern (format "kubed-create-%S"         resource)))
+        (crt-spec nil)
+        (namespaced t)
+        (keyword nil))
+
+    ;; Process keyword arguments.
+    (while (keywordp (car commands))
+      (setq keyword (pop commands))
+      (cond
+       ((eq keyword :namespaced) (setq namespaced (pop commands)))
+       ((eq keyword :create)     (setq crt-spec   (pop commands)))
+       ;; FIXME: Add error for unknown keywords
+       (t (pop commands))))
+
+    ;; Extend `commands' with standard commands.
+    (dolist (c `((get "RET" "Switch to buffer showing description of"
+                      (switch-to-buffer
+                       ,(if namespaced
+                            `(,desc-fun ,resource k8sns)
+                          `(,desc-fun ,resource))))
+                 (get-in-other-window
+                  "o" "Pop to buffer showing description of"
+                  (switch-to-buffer-other-window
+                   ,(if namespaced
+                        `(,desc-fun ,resource k8sns)
+                      `(,desc-fun ,resource))))
+                 (display "C-o" "Display description of"
+                          (display-buffer
+                           ,(if namespaced
+                                `(,desc-fun ,resource k8sns)
+                              `(,desc-fun ,resource))))
+                 (edit "e" "Edit"
+                       ,(if namespaced
+                            `(,edt-name ,resource k8sns)
+                          `(,edt-name ,resource)))
+                 (delete "D" "Delete"
+                         ,(if namespaced
+                              `(if k8sns
+                                   (when (y-or-n-p
+                                          (format ,(concat "Delete Kubernetes "
+                                                           (symbol-name resource)
+                                                           " `%s' in namespace `%s'?")
+                                                  ,resource k8sns))
+                                     (,dlt-name (list (list ,resource k8sns))))
+                                 (when (y-or-n-p
+                                        (format ,(concat "Delete Kubernetes "
+                                                         (symbol-name resource)
+                                                         " `%s'?")
+                                                ,resource))
+                                   (,dlt-name (list ,resource))))
+                            `(when (y-or-n-p
+                                    (format ,(concat "Delete Kubernetes "
+                                                     (symbol-name resource)
+                                                     " `%s'?")
+                                            ,resource))
+                               (,dlt-name (list ,resource)))))))
+      (push c commands))
+
+    ;; Generate code.
     `(progn
        (defvar ,hist-var nil
          ,(format "History list for `%S'." read-fun))
@@ -209,8 +257,7 @@ namespaceless resource type, put `:namespaced nil' before COMMANDS:
 
        (defun ,sure-fun ()
          ,(format "Populate `%S', if not already populated." list-var)
-         (unless (or ,list-var (process-live-p ,proc-var))
-           (,updt-cmd)))
+         (unless (or ,list-var (process-live-p ,proc-var)) (,updt-cmd)))
 
        (defun ,updt-cmd ()
          ,(format "Update `%S'." list-var)
@@ -367,26 +414,37 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
                                                           ,list-var)
                                                 s p)))
                       nil 'confirm nil ',hist-var default))
-                    (split (mapcar (lambda (c) (split-string c " ")) (ensure-list choice))))
+                    (split (mapcar (lambda (c)
+                                     (split-string c " "))
+                                   (ensure-list choice))))
                (if multi split (car split)))))
 
        ,(if namespaced
             `(defun ,cmd-name (,resource &optional k8sns)
-               ,(format "Display Kubernetes %S %s." resource (upcase (symbol-name resource)))
+               ,(format "Display Kubernetes %S %s."
+                        resource (upcase (symbol-name resource)))
                (interactive (if kubed-all-namespaces-mode
                                 (,read-nms "Display")
                               (list (,read-fun "Display"))))
                (display-buffer (,desc-fun ,resource k8sns)))
           `(defun ,cmd-name (,resource)
-             ,(format "Display Kubernetes %S %s." resource (upcase (symbol-name resource)))
+             ,(format "Display Kubernetes %S %s."
+                      resource (upcase (symbol-name resource)))
              (interactive (list (,read-fun "Display")))
              (display-buffer (,desc-fun ,resource))))
 
        (add-hook 'kubed-update-hook #',updt-cmd)
 
+       ,(when namespaced
+          `(add-hook 'kubed-all-namespaces-mode-hook
+                     (lambda ()
+                       (setq ,list-var nil)
+                       (,updt-cmd))))
+
        ,(if namespaced
             `(defun ,edt-name (,resource &optional k8sns)
-               ,(format "Edit Kubernetes %S %s." resource (upcase (symbol-name resource)))
+               ,(format "Edit Kubernetes %S %s."
+                        resource (upcase (symbol-name resource)))
                (interactive (if kubed-all-namespaces-mode
                                 (,read-nms "Edit")
                               (list (,read-fun "Edit"))))
@@ -413,7 +471,8 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
 
        ,(if namespaced
             `(defun ,dlt-name (,plrl-var)
-               ,(format "Delete Kubernetes %S %s." plrl-var (upcase (symbol-name plrl-var)))
+               ,(format "Delete Kubernetes %S %s."
+                        plrl-var (upcase (symbol-name plrl-var)))
                (interactive (if kubed-all-namespaces-mode
                                 (,read-nms "Delete" nil t)
                               (list (,read-crm "Delete"))))
@@ -421,29 +480,58 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
                  (user-error ,(format "You didn't specify %S to delete" plrl-var)))
                (if kubed-all-namespaces-mode
                    (pcase-dolist (`(,name ,space) ,plrl-var)
-                     (message ,(concat "Deleting Kubernetes " (symbol-name resource) " `%s' in namespace `%s'...") name space)
+                     (message ,(concat "Deleting Kubernetes "
+                                       (symbol-name resource)
+                                       " `%s' in namespace `%s'...")
+                              name space)
                      (if (zerop (apply #'call-process
                                        kubed-kubectl-executable nil nil nil
-                                       "delete" "--namespace" space ,(symbol-name plrl-var) name))
-                         (message ,(concat "Deleting Kubernetes " (symbol-name resource) " `%s' in namespace `%s'... Done.") name space)
-                       (error ,(concat "Failed to delete Kubernetes" (symbol-name resource) " `%s' in namespace `%s'") name space))))
-               (message ,(concat "Deleting Kubernetes " (symbol-name plrl-var) " `%s'...") (string-join ,plrl-var "', `"))
+                                       "delete" "--namespace" space
+                                       ,(symbol-name plrl-var) name))
+                         (message ,(concat "Deleting Kubernetes "
+                                           (symbol-name resource)
+                                           " `%s' in namespace `%s'... Done.")
+                                  name space)
+                       (error ,(concat "Failed to delete Kubernetes"
+                                       (symbol-name resource)
+                                       " `%s' in namespace `%s'")
+                              name space))))
+               (message ,(concat "Deleting Kubernetes "
+                                 (symbol-name plrl-var)
+                                 " `%s'...")
+                        (string-join ,plrl-var "', `"))
                (if (zerop (apply #'call-process
                                  kubed-kubectl-executable nil nil nil
                                  "delete" ,(symbol-name plrl-var) ,plrl-var))
-                   (message ,(concat "Deleting Kubernetes " (symbol-name plrl-var) " `%s'... Done.") (string-join ,plrl-var "', `"))
-                 (error ,(concat "Failed to delete Kubernetes" (symbol-name plrl-var) " `%s'") (string-join ,plrl-var "', `"))))
+                   (message ,(concat "Deleting Kubernetes "
+                                     (symbol-name plrl-var)
+                                     " `%s'... Done.")
+                            (string-join ,plrl-var "', `"))
+                 (error ,(concat "Failed to delete Kubernetes"
+                                 (symbol-name plrl-var)
+                                 " `%s'")
+                        (string-join ,plrl-var "', `"))))
           `(defun ,dlt-name (,plrl-var)
-             ,(format "Delete Kubernetes %S %s." plrl-var (upcase (symbol-name plrl-var)))
+             ,(format "Delete Kubernetes %S %s." plrl-var
+                      (upcase (symbol-name plrl-var)))
              (interactive (list (,read-crm "Delete")))
              (unless ,plrl-var
                (user-error ,(format "You didn't specify %S to delete" plrl-var)))
-             (message ,(concat "Deleting Kubernetes " (symbol-name plrl-var) " `%s'...") (string-join ,plrl-var "', `"))
+             (message ,(concat "Deleting Kubernetes "
+                               (symbol-name plrl-var)
+                               " `%s'...")
+                      (string-join ,plrl-var "', `"))
              (if (zerop (apply #'call-process
                                kubed-kubectl-executable nil nil nil
                                "delete" ,(symbol-name plrl-var) ,plrl-var))
-                 (message ,(concat "Deleting Kubernetes " (symbol-name plrl-var) " `%s'... Done.") (string-join ,plrl-var "', `"))
-               (error ,(concat "Failed to delete Kubernetes" (symbol-name plrl-var) " `%s'") (string-join ,plrl-var "', `")))))
+                 (message ,(concat "Deleting Kubernetes "
+                                   (symbol-name plrl-var)
+                                   " `%s'... Done.")
+                          (string-join ,plrl-var "', `"))
+               (error ,(concat "Failed to delete Kubernetes"
+                               (symbol-name plrl-var)
+                               " `%s'")
+                      (string-join ,plrl-var "', `")))))
 
        (defvar-local ,ents-var nil)
 
@@ -458,80 +546,6 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
                             (apply #'vector c)))
           ,ents-var))
 
-       (defun ,dlt-cmd ()
-         ,(format "Delete Kubernetes %S at point." resource)
-         (interactive "" ,mod-name)
-         (if-let ,(if namespaced
-                      `((k8sent (tabulated-list-get-entry))
-                        (,resource (aref k8sent 0)))
-                    `(,resource (tabulated-list-get-id)))
-             ,(if namespaced
-                  `(if-let ((k8sns (and kubed-all-namespaces-mode
-                                        (aref (tabulated-list-get-entry) 1))))
-                       (when (y-or-n-p (format ,(concat "Delete Kubernetes " (symbol-name resource) " `%s' in namespace `%s'?") ,resource k8sns))
-                         (,dlt-name (list (list ,resource k8sns))))
-                     (when (y-or-n-p (format ,(concat "Delete Kubernetes " (symbol-name resource) " `%s'?") ,resource))
-                       (,dlt-name (list ,resource))))
-                `(when (y-or-n-p (format ,(concat "Delete Kubernetes " (symbol-name resource) " `%s'?") ,resource))
-                   (,dlt-name (list ,resource))))
-           (user-error ,(format "No Kubernetes %S at point" resource))))
-
-       (defun ,slct-cmd ()
-         ,(format "Switch to buffer showing description of Kubernetes %s at point." resource)
-         (interactive "" ,mod-name)
-         (if-let ,(if namespaced
-                      `((k8sent (tabulated-list-get-entry))
-                        (,resource (aref k8sent 0)))
-                    `(,resource (tabulated-list-get-id)))
-             ,(if namespaced
-                  `(let ((k8sns (when kubed-all-namespaces-mode
-                                  (aref (tabulated-list-get-entry) 1))))
-                     (switch-to-buffer (,desc-fun ,resource k8sns)))
-                `(switch-to-buffer (,desc-fun ,resource)))
-           (user-error ,(format "No Kubernetes %S at point" resource))))
-
-       (defun ,othr-cmd ()
-         ,(format "Pop to buffer showing description of Kubernetes %s at point." resource)
-         (interactive "" ,mod-name)
-         (if-let ,(if namespaced
-                      `((k8sent (tabulated-list-get-entry))
-                        (,resource (aref k8sent 0)))
-                    `(,resource (tabulated-list-get-id)))
-             ,(if namespaced
-                  `(let ((k8sns (when kubed-all-namespaces-mode
-                                  (aref (tabulated-list-get-entry) 1))))
-                     (switch-to-buffer-other-window (,desc-fun ,resource k8sns)))
-                `(switch-to-buffer-other-window (,desc-fun ,resource)))
-           (user-error ,(format "No Kubernetes %S at point" resource))))
-
-       (defun ,desc-cmd ()
-         ,(format "Display Kubernetes %S at point." resource)
-         (interactive "" ,mod-name)
-         (if-let ,(if namespaced
-                      `((k8sent (tabulated-list-get-entry))
-                        (,resource (aref k8sent 0)))
-                    `(,resource (tabulated-list-get-id)))
-             ,(if namespaced
-                  `(let ((k8sns (when kubed-all-namespaces-mode
-                                  (aref (tabulated-list-get-entry) 1))))
-                     (display-buffer (,desc-fun ,resource k8sns)))
-                `(display-buffer (,desc-fun ,resource)))
-           (user-error ,(format "No Kubernetes %S at point" resource))))
-
-       (defun ,edit-cmd ()
-         ,(format "Edit Kubernetes %S at point." resource)
-         (interactive "" ,mod-name)
-         (if-let ,(if namespaced
-                      `((k8sent (tabulated-list-get-entry))
-                        (,resource (aref k8sent 0)))
-                    `(,resource (tabulated-list-get-id)))
-             ,(if namespaced
-                  `(let ((k8sns (when kubed-all-namespaces-mode
-                                  (aref (tabulated-list-get-entry) 1))))
-                     (,edt-name ,resource k8sns))
-                `(,edt-name ,resource))
-           (user-error ,(format "No Kubernetes %S at point" resource))))
-
        (defun ,mark-cmd ()
          ,(format "Mark Kubernetes %S at point for deletion." resource)
          (interactive "" ,mod-name)
@@ -644,6 +658,14 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
                                       (display-buffer ,dlt-errb))))))))
              (user-error ,(format "No Kubernetes %S marked for deletion" plrl-var)))))
 
+       ,(if crt-spec `(defun ,crt-name . ,crt-spec)
+          `(defun ,crt-name (definition)
+             ,(format "Create Kubernetes %s from definition file DEFINITION."
+                      (symbol-name resource))
+             (interactive (list (kubed-read-resource-definition-file-name
+                                 ,(capitalize (symbol-name resource)))))
+             (kubed-create definition ,(symbol-name resource))))
+
        ,@(mapcar
           (pcase-lambda (`(,suffix ,_key ,desc . ,body))
             `(defun ,(intern (format "kubed-%Ss-%S" resource suffix)) ()
@@ -663,15 +685,12 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
 
        (defvar-keymap ,(intern (format "kubed-%Ss-mode-map" resource))
          :doc ,(format "Keymap for `%S" mod-name)
-         "RET" #',slct-cmd
-         "o"   #',othr-cmd
-         "C-o" #',desc-cmd
+         "A"   #'kubed-all-namespaces-mode
          "G"   #',updt-cmd
          "d"   #',mark-cmd
          "x"   #',exec-cmd
          "u"   #',umrk-cmd
-         "e"   #',edit-cmd
-         "D"   #',dlt-cmd
+         "+"   #',crt-name
          ,@(mapcan
             (pcase-lambda (`(,suffix ,key ,_desc . ,_body))
               (when key
@@ -693,7 +712,6 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
                                    `(let ((c (+ ,i (if kubed-all-namespaces-mode 1 0))))
                                       (funcall ,sorter (aref (cadr l) c) (aref (cadr r) c)))
                                  `(funcall ,sorter (aref (cadr l) ,i) (aref (cadr r) ,i))))
-
                          t))
                  (nthcdr 4 p))
                 res))
@@ -736,6 +754,7 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
          (setq tabulated-list-format (,frmt-fun))
          (setq tabulated-list-entries #',ents-fun)
          (setq tabulated-list-padding 2)
+         (setq-local truncate-string-ellipsis (propertize ">" 'face 'shadow))
          (tabulated-list-init-header))
 
        (defun ,buff-fun (,plrl-var &optional buffer frozen)
@@ -771,6 +790,7 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
 ;;;###autoload (autoload 'kubed-edit-pod "kubed" nil t)
 ;;;###autoload (autoload 'kubed-delete-pods "kubed" nil t)
 ;;;###autoload (autoload 'kubed-list-pods "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-pod "kubed" nil t)
 (kubed-define-resource pod
     ((phase ".status.phase" 10) (starttime ".status.startTime" 20))
   (dired "C-d" "Start Dired in home directory of first container of"
@@ -799,24 +819,39 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
 ;;;###autoload (autoload 'kubed-edit-namespace "kubed" nil t)
 ;;;###autoload (autoload 'kubed-delete-namespaces "kubed" nil t)
 ;;;###autoload (autoload 'kubed-list-namespaces "kubed" nil t)
-(kubed-define-resource namespace () :namespaced nil)
+;;;###autoload (autoload 'kubed-create-namespace "kubed" nil t)
+(kubed-define-resource namespace ()
+  :namespaced nil
+  :create
+  ((name) "Create Kubernetes namespace with name NAME."
+   (interactive (list (read-string "Create namespace with name: ")))
+   (unless (zerop
+            (call-process
+             kubed-kubectl-executable nil nil nil
+             "create" "namespace" name))
+     (user-error "Failed to create Kubernetes namespace with name `%s'" name))
+   (message "Created Kubernetes namespace with name `%s'." name)
+   (kubed-update-namespaces)))
 
 ;;;###autoload (autoload 'kubed-display-persistentvolume "kubed" nil t)
 ;;;###autoload (autoload 'kubed-edit-persistentvolume "kubed" nil t)
 ;;;###autoload (autoload 'kubed-delete-persistentvolumes "kubed" nil t)
 ;;;###autoload (autoload 'kubed-list-persistentvolumes "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-persistentvolume "kubed" nil t)
 (kubed-define-resource persistentvolume () :namespaced nil)
 
 ;;;###autoload (autoload 'kubed-display-service "kubed" nil t)
 ;;;###autoload (autoload 'kubed-edit-service "kubed" nil t)
 ;;;###autoload (autoload 'kubed-delete-services "kubed" nil t)
 ;;;###autoload (autoload 'kubed-list-services "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-service "kubed" nil t)
 (kubed-define-resource service)
 
 ;;;###autoload (autoload 'kubed-display-secret "kubed" nil t)
 ;;;###autoload (autoload 'kubed-edit-secret "kubed" nil t)
 ;;;###autoload (autoload 'kubed-delete-secrets "kubed" nil t)
 ;;;###autoload (autoload 'kubed-list-secrets "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-secret "kubed" nil t)
 (kubed-define-resource secret
     ((type ".type" 32) (creationtimestamp ".metadata.creationTimestamp" 20)))
 
@@ -824,6 +859,7 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
 ;;;###autoload (autoload 'kubed-edit-job "kubed" nil t)
 ;;;###autoload (autoload 'kubed-delete-jobs "kubed" nil t)
 ;;;###autoload (autoload 'kubed-list-jobs "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-job "kubed" nil t)
 (kubed-define-resource job
     ((status ".status.conditions[0].type" 10) (starttime ".status.startTime" 20)))
 
@@ -831,12 +867,18 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
 ;;;###autoload (autoload 'kubed-edit-deployment "kubed" nil t)
 ;;;###autoload (autoload 'kubed-delete-deployments "kubed" nil t)
 ;;;###autoload (autoload 'kubed-list-deployments "kubed" nil t)
-(kubed-define-resource deployment)
+;;;###autoload (autoload 'kubed-create-deployment "kubed" nil t)
+(kubed-define-resource deployment
+    ((reps ".status.replicas" 4
+           (lambda (l r) (< (string-to-number l) (string-to-number r)))
+           :right-align t)
+     (creationtimestamp ".metadata.creationTimestamp" 20)))
 
 ;;;###autoload (autoload 'kubed-display-replicaset "kubed" nil t)
 ;;;###autoload (autoload 'kubed-edit-replicaset "kubed" nil t)
 ;;;###autoload (autoload 'kubed-delete-replicasets "kubed" nil t)
 ;;;###autoload (autoload 'kubed-list-replicasets "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-replicaset "kubed" nil t)
 (kubed-define-resource replicaset
     ((reps ".status.replicas" 4
            (lambda (l r) (< (string-to-number l) (string-to-number r)))
@@ -849,6 +891,7 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
 ;;;###autoload (autoload 'kubed-edit-statefulset "kubed" nil t)
 ;;;###autoload (autoload 'kubed-delete-statefulsets "kubed" nil t)
 ;;;###autoload (autoload 'kubed-list-statefulsets "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-statefulset "kubed" nil t)
 (kubed-define-resource statefulset
     ((reps ".status.replicas" 4
            (lambda (l r) (< (string-to-number l) (string-to-number r)))
@@ -907,18 +950,67 @@ Optional argument DEFAULT is the minibuffer default argument."
   (message "Kubernetes namespace is now `%s'." ns)
   (kubed-update-all))
 
+(defcustom kubed-read-resource-definition-filter-files-by-kind t
+  "Whether to filter file completion candidates by their Kubernetes \"kind\".
+
+If this is non-nil, `kubed-read-resource-definition-file-name' only
+suggests files with the right \"kind\" as completion candidates when you
+call it with non-nil KIND argument.  This is useful because you get more
+relevant completions, but it may become slow in directories with many
+large JSON and YAML files, in which case you can set this option to nil."
+  :type 'boolean)
+
+(defun kubed-read-resource-definition-file-name (&optional kind)
+  "Prompt for Kubernetes resource definition file name.
+
+Optional argument KIND is the kind of resource the file should define.
+If `kubed-read-resource-definition-filter-files-by-kind' is non-nil,
+this function suggests only files that define resources of kind KIND as
+completion candidates."
+  (read-file-name
+   (format "%s definition file: " (or kind "Resource")) nil nil t nil
+   (if (and kind kubed-read-resource-definition-filter-files-by-kind
+            (executable-find "grep"))
+       (let ((cache (make-hash-table :test 'equal)))
+         (lambda (f)
+           (or (file-directory-p f)
+               (when-let ((ext (and (string-match "\\.[^.]*\\'" f)
+                                    (substring f (1+ (match-beginning 0))))))
+                 (or (and (member ext '("yaml" "yml"))
+                          (pcase (gethash (expand-file-name f) cache 'noval)
+                            ('noval
+                             (puthash (expand-file-name f)
+                                      (zerop (call-process
+                                              "grep" f nil nil
+                                              (format "^kind: %s$" kind)))
+                                      cache))
+                            (val val)))
+                     (and (equal ext "json")
+                          (pcase (gethash (expand-file-name f) cache 'noval)
+                            ('noval
+                             (puthash (expand-file-name f)
+                                      (zerop (call-process
+                                              "grep" f nil nil
+                                              (format "^kind: %s$" kind)))
+                                      cache))
+                            (val val))))))))
+     (lambda (f)
+       (or (file-directory-p f)
+           (when (string-match "\\.[^.]*\\'" f)
+             (member (substring f (1+ (match-beginning 0)))
+                     '("yaml" "yml" "json"))))))))
+
 ;;;###autoload
-(defun kubed-create-pod (definition)
-  "Create Kubernetes pod with definition DEFINITION."
-  (interactive (list (read-file-name "Pod definition file: ")))
-  (message "Creating pod with definition `%s'..." definition)
-  (message "Creating pod with definition `%s'... Done.  New pod name is `%s'."
-           definition (car (process-lines kubed-kubectl-executable
-                                          "create" "-f"
-                                          (expand-file-name definition)
-                                          "-o" "jsonpath={.metadata.name}"))))
-
-(keymap-set kubed-pods-mode-map "+" #'kubed-create-pod)
+(defun kubed-create (definition &optional kind)
+  "Create Kubernetes resource of kind KIND with definition DEFINITION."
+  (interactive
+   (list (kubed-read-resource-definition-file-name)))
+  (message "Creating Kubernetes %s with definition `%s'..." (or kind "resource") definition)
+  (message "Creating Kubernetes %s with definition `%s'... Done.  New %s name is `%s'."
+           (or kind "resource") definition (or kind "resource")
+           (car (process-lines kubed-kubectl-executable
+                               "create" "-f" (expand-file-name definition)
+                               "-o" "jsonpath={.metadata.name}"))))
 
 (defun kubed-pod-containers (pod &optional k8sns)
   "Return list of containers in Kubernetes pod POD in namespace K8SNS."