├── CHANGELOG.md ├── LICENSE ├── README.md ├── UltiSnips └── yaml.snippets ├── ftdetect └── kubeconf.vim └── ftplugin └── yaml └── kube.vim /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v1.2.0 2 | 3 | - Add async option for those on vim 8+ 4 | 5 | v1.1.0 6 | 7 | - Added commands for KubeDelete, KubeCreate, and directory-specific variants *Dir 8 | - vim-kubernetes commands and code should now only load for yaml files 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Andrew Stuart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-kubernetes 2 | 3 | This package provides kubernetes YAML snippets (requires 4 | [ultisnips](https://github.com/sirver/UltiSnips), as well as a number of 5 | integrations with kubectl (requires that you actually have kubectl installed and 6 | on your `PATH`). 7 | 8 | ## Quick Start 9 | 10 | If you are using Vundle, add this to your `~/.vimrc`: 11 | 12 | ```vim 13 | Plugin 'andrewstuart/vim-kubernetes' 14 | ``` 15 | 16 | If you are using pathogen, clone this repo to your `~/.vim/bundle` 17 | 18 | ## Current integrations: 19 | 20 | ### Functions/Commands 21 | For the current buffer (including modifications not on disk) 22 | - `:KubeApply` 23 | - `:KubeDelete` 24 | - `:KubeCreate` 25 | 26 | And for the current directory (read from disk) 27 | - `:KubeApplyDir` 28 | - `:KubeDeleteDir` 29 | 30 | If you would like to bind any of these to a shortcut, you can do something like 31 | the following in your `~/.vimrc`: 32 | 33 | ```vim 34 | au FileType yaml nmap r :KubeApply 35 | ``` 36 | -------------------------------------------------------------------------------- /UltiSnips/yaml.snippets: -------------------------------------------------------------------------------- 1 | snippet dep "Deployment" !bm 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: ${1:some-controller} 6 | namespace: ${2:default} 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | $3 12 | template: 13 | metadata: 14 | labels: 15 | ${3:app: $1} 16 | spec: 17 | containers: 18 | - name: ${4:name} 19 | image: ${5:nginx} 20 | imagePullPolicy: Always 21 | resources: 22 | requests: 23 | cpu: 100m 24 | memory: 200Mi 25 | ports: 26 | - containerPort: 8080 27 | endsnippet 28 | 29 | snippet selm "Selector MatchLabels" !b 30 | selector: 31 | matchLabels: 32 | $0 33 | endsnippet 34 | 35 | snippet svc "Service" !b 36 | apiVersion: v1 37 | kind: Service 38 | metadata: 39 | name: ${1:frontend} 40 | namespace: ${2:default} 41 | labels: 42 | app: ${3:someApp} 43 | tier: ${4:frontend} 44 | spec: 45 | ports: 46 | - port: ${5:80} 47 | selector: 48 | app: $3 49 | tier: $4 50 | endsnippet 51 | 52 | snippet depsvc "Deployment and service" !b 53 | apiVersion: apps/v1 54 | kind: Deployment 55 | metadata: 56 | name: ${1:some-controller} 57 | namespace: ${2:default} 58 | spec: 59 | replicas: 1 60 | selector: 61 | matchLabels: 62 | $3 63 | template: 64 | metadata: 65 | labels: 66 | ${3:app: $1} 67 | spec: 68 | containers: 69 | - name: ${4:name} 70 | image: ${5:nginx} 71 | imagePullPolicy: Always 72 | resources: 73 | requests: 74 | cpu: 100m 75 | memory: 200Mi 76 | ports: 77 | - containerPort: ${6:8080} 78 | --- 79 | apiVersion: v1 80 | kind: Service 81 | metadata: 82 | name: $1 83 | namespace: $2 84 | spec: 85 | ports: 86 | - port: ${7:80} 87 | targetPort: $6 88 | selector: 89 | $3 90 | endsnippet 91 | 92 | snippet depsvcing "Deployment, service, and ingress" !b 93 | apiVersion: apps/v1 94 | kind: Deployment 95 | metadata: 96 | name: ${1:some-controller} 97 | namespace: ${2:default} 98 | spec: 99 | replicas: 1 100 | selector: 101 | matchLabels: 102 | $3 103 | template: 104 | metadata: 105 | labels: 106 | ${3:app: $1} 107 | spec: 108 | containers: 109 | - name: ${4:name} 110 | image: ${5:nginx} 111 | imagePullPolicy: Always 112 | resources: 113 | requests: 114 | cpu: 100m 115 | memory: 200Mi 116 | ports: 117 | - containerPort: ${6:8080} 118 | --- 119 | apiVersion: v1 120 | kind: Service 121 | metadata: 122 | name: $1 123 | namespace: $2 124 | spec: 125 | ports: 126 | - port: ${7:80} 127 | targetPort: $6 128 | selector: 129 | $3 130 | --- 131 | apiVersion: networking.k8s.io/v1 132 | kind: Ingress 133 | metadata: 134 | annotations: 135 | nginx.ingress.kubernetes.io/rewrite-target: / 136 | kubernetes.io/ingress.class: nginx 137 | cert-manager.io/cluster-issuer: "letsencrypt-prod" 138 | name: $1 139 | namespace: $2 140 | spec: 141 | ${10:tls: 142 | - secretName: ${9:$8.tls} 143 | hosts: 144 | - $8 145 | }rules: 146 | - host: ${8:host} 147 | http: 148 | paths: 149 | - path: ${11:/} 150 | pathType: Prefix 151 | backend: 152 | service: 153 | name: $1 154 | port: 155 | number: $7 156 | endsnippet 157 | 158 | snippet pv "PersistentVolume" !b 159 | apiVersion: v1 160 | kind: PersistentVolume 161 | metadata: 162 | name: ${1:name} 163 | labels: 164 | app: ${2:app} 165 | tier: ${3:tier} 166 | spec: 167 | capacity: 168 | storage: ${4:20Gi} 169 | accessModes: 170 | - ${5:ReadWriteMany} 171 | nfs: 172 | server: ${6:NameOrIP} 173 | path: ${7:"/share/path/on/server"} 174 | endsnippet 175 | 176 | snippet pvc "PersistentVolumeClaim" !b 177 | apiVersion: v1 178 | kind: PersistentVolumeClaim 179 | metadata: 180 | name: ${1:name} 181 | labels: 182 | # insert any desired labels to identify your claim 183 | app: ${2:app} 184 | tier: ${3:tier} 185 | spec: 186 | ${4:storageClassName: ${5:standard}} 187 | accessModes: 188 | - ${6:ReadWriteOnce} 189 | resources: 190 | requests: 191 | # The amount of the volume's storage to request 192 | storage: ${7:20Gi} 193 | endsnippet 194 | 195 | snippet ing "Ingress" !b 196 | apiVersion: networking.k8s.io/v1 197 | kind: Ingress 198 | metadata: 199 | annotations: 200 | nginx.ingress.kubernetes.io/rewrite-target: / 201 | kubernetes.io/ingress.class: nginx 202 | cert-manager.io/cluster-issuer: "letsencrypt-prod" 203 | name: ${1:name} 204 | namespace: ${2:default} 205 | spec: 206 | ${5:tls: 207 | - secretName: ${4:$3.tls} 208 | hosts: 209 | - $3 210 | }rules: 211 | - host: ${3:host.tld} 212 | http: 213 | paths: 214 | - path: ${7:/} 215 | pathType: Prefix 216 | backend: 217 | service: 218 | name: ${8:service} 219 | port: 220 | number: ${9:portNumberOrName} 221 | endsnippet 222 | 223 | snippet ns "Namespace" !b 224 | apiVersion: v1 225 | kind: Namespace 226 | metadata: 227 | name: ${1:name} 228 | endsnippet 229 | 230 | snippet sa "ServiceAccount" !b 231 | apiVersion: v1 232 | kind: ServiceAccount 233 | metadata: 234 | name: ${1:name} 235 | endsnippet 236 | 237 | snippet ingtls "Ingress TLS section" !b 238 | tls: 239 | - secretName: ${2:$1.tls} 240 | hosts: 241 | - ${1:host} 242 | endsnippet 243 | 244 | snippet cfg "ConfigMap" !b 245 | apiVersion: v1 246 | kind: ConfigMap 247 | metadata: 248 | name: ${1:name} 249 | data: 250 | ${2:key}: ${3:value} 251 | endsnippet 252 | 253 | snippet sec "Secret" !b 254 | apiVersion: v1 255 | kind: Secret 256 | metadata: 257 | name: ${1:secret-name} 258 | type: ${2:Opaque} 259 | data: 260 | ${3:key}: ${4:value} 261 | endsnippet 262 | 263 | snippet env "Environment template" !b 264 | - name: ${1:VAR_NAME} 265 | value: ${2:value} 266 | endsnippet 267 | 268 | snippet secref "env SecretRef" !b 269 | valueFrom: 270 | secretKeyRef: 271 | name: ${1:secret-name} 272 | key: ${2:key-name} 273 | endsnippet 274 | 275 | snippet pvol "Pod Volume Object" 276 | - name: ${1:name} 277 | ${2:source}: 278 | name: 279 | endsnippet 280 | 281 | snippet job "Kubernetes Job" !b 282 | apiVersion: batch/v1 283 | kind: Job 284 | metadata: 285 | name: ${1:jobname} 286 | labels: 287 | ${2:sometag: somevalue} 288 | spec: 289 | template: 290 | metadata: 291 | name: $1 292 | spec: 293 | containers: 294 | - name: ${3:containerName} 295 | image: ${4: image} 296 | imagePullPolicy: Always 297 | command: 298 | - ${5:"override" 299 | - "--the" 300 | - "entrypoint"} 301 | restartPolicy: OnFailure 302 | endsnippet 303 | 304 | snippet cron "Kubernetes Cronjob" !b 305 | apiVersion: batch/v2alpha1 306 | kind: CronJob 307 | metadata: 308 | name: ${1:name} 309 | spec: 310 | schedule: "${2:*/1} * * * *" 311 | jobTemplate: 312 | spec: 313 | template: 314 | spec: 315 | containers: 316 | - name: $1 317 | image: ${3: image} 318 | args: ${4: 319 | - /bin/sh 320 | - -c 321 | - date; echo Hello from the Kubernetes cluster} 322 | restartPolicy: OnFailure 323 | endsnippet 324 | 325 | snippet skr "SecretKeyRef" 326 | valueFrom: 327 | secretKeyRef: 328 | name: ${1:secret} 329 | key: ${2:key} 330 | endsnippet 331 | 332 | snippet cert "cert-manager certificate" !b 333 | apiVersion: cert-manager.io/v1 334 | kind: Certificate 335 | metadata: 336 | name: ${1:name} 337 | namespace: ${2:namespace} 338 | spec: 339 | secretName: ${4:$3.tls} 340 | isCA: false 341 | duration: 2160h # 90d 342 | renewBefore: 360h # 15d 343 | # subject: 344 | # organizations: 345 | # - cluster.local 346 | dnsNames: 347 | - ${3:some.domain.com} 348 | issuerRef: 349 | name: ${5:letsencrypt} 350 | kind: ${6:ClusterIssuer} 351 | endsnippet 352 | 353 | 354 | snippet netp "NetworkPolicy" !b 355 | kind: NetworkPolicy 356 | apiVersion: networking.k8s.io/v1 357 | metadata: 358 | namespace: ${1:ns} 359 | name: ${2:mypolicy} 360 | spec: 361 | podSelector: 362 | matchLabels: 363 | ${3:app}: ${4:loki} 364 | ingress: 365 | - namespaceSelector: 366 | matchLabels: 367 | ${5:$4}: ${6:"true"} 368 | endsnippet 369 | 370 | snippet probe "Liveness/Readiness Probes" !b 371 | livenessProbe: &probe 372 | initialDelaySeconds: ${1:10} 373 | httpGet: 374 | port: ${2:8080} 375 | path: ${3:/} 376 | readinessProbe: *probe $0 377 | endsnippet 378 | 379 | snippet ss "StatefulSet" !b 380 | apiVersion: v1 381 | kind: Service 382 | metadata: 383 | name: ${1:myservice} 384 | spec: 385 | ports: 386 | - port: $5 387 | name: $6 388 | clusterIP: None 389 | selector: 390 | $2 391 | --- 392 | apiVersion: apps/v1 393 | kind: StatefulSet 394 | metadata: 395 | name: ${1:mystatefulset} 396 | spec: 397 | selector: 398 | matchLabels: 399 | $2 400 | serviceName: "nginx" 401 | replicas: 3 # by default is 1 402 | template: 403 | metadata: 404 | labels: 405 | ${2:app: $1} 406 | spec: 407 | # terminationGracePeriodSeconds: 10 408 | containers: 409 | - name: ${3:$1} 410 | image: ${4:$1} 411 | ports: 412 | - containerPort: ${5:80} 413 | name: ${6:web} 414 | volumeMounts: 415 | - name: ${7:volume} 416 | mountPath: ${8:/var/lib/mydata} 417 | volumeClaimTemplates: 418 | - metadata: 419 | name: $7 420 | spec: 421 | accessModes: [ "ReadWriteOnce" ] 422 | storageClassName: "${9:standard}" 423 | resources: 424 | requests: 425 | storage: ${10:1G} 426 | endsnippet 427 | 428 | snippet res "Resources" !b 429 | resources: 430 | requests: 431 | cpu: ${1:100m} 432 | memory: ${2:200Mi} 433 | ${5:limits: 434 | cpu: ${3:$1} 435 | memory: ${4:$2}}$0 436 | endsnippet 437 | 438 | snippet init "Init Container" !b 439 | initContainers: 440 | - name: ${1:myinit} 441 | image: ${2:busybox} 442 | command: [${3:rm, -rf, $5/lost+found}] 443 | ${6:volumeMounts: 444 | - name: ${4:data} 445 | mountPath: ${5:/data}}$0 446 | endsnippet 447 | 448 | snippet strat "Deployment Strategy" !b 449 | strategy: 450 | type: ${1:RollingUpdate|Recreate} 451 | rollingUpdate: 452 | maxSurge: ${2:1} 453 | maxUnavailable: ${3:1}$0 454 | endsnippet 455 | 456 | snippet atls "tls-acme annotations" !b 457 | annotations: 458 | kubernetes.io/tls-acme: "true" 459 | endsnippet 460 | 461 | snippet vtls "tls-vault annotations" !b 462 | annotations: 463 | kubernetes.io/tls-vault: "true" 464 | endsnippet 465 | 466 | snippet cmtls "cert-manager tls annotations" !b 467 | ${2:annotations: 468 | }cert-manager.io/cluster-issuer: ${1:lets-encrypt} 469 | endsnippet 470 | 471 | snippet edns "external dns" !b 472 | annotations: 473 | external-dns.alpha.kubernetes.io/hostname: ${1:myname.mydomain.com} 474 | endsnippet 475 | 476 | snippet role "Role" !b 477 | kind: ${1:Cluster}Role 478 | apiVersion: rbac.authorization.k8s.io/v1 479 | metadata: 480 | ${2:namespace: ${3:default} 481 | }name: ${4:configmap-updater} 482 | rules: 483 | - apiGroups: ["${5:}"] 484 | resources: ["${6:configmaps}"] 485 | resourceNames: ["${7:my-configmap}"] 486 | verbs: [${8:"update", "get"}] 487 | endsnippet 488 | 489 | snippet rb "RoleBinding" !b 490 | # This role binding allows "jane" to read pods in the "default" namespace. 491 | kind: ${1:Cluster}RoleBinding 492 | apiVersion: rbac.authorization.k8s.io/v1 493 | metadata: 494 | name: ${2:read-pods} 495 | ${3:namespace: ${4:pods} 496 | }subjects: 497 | - kind: ${5:User|ServiceAccount|Group} 498 | name: ${6:jane} # Name is case sensitive 499 | apiGroup: rbac.authorization.k8s.io 500 | roleRef: 501 | kind: ${7:Cluster}Role #this must be Role or ClusterRole 502 | name: ${8:pod-reader} # this must match the name of the Role or ClusterRole you wish to bind to 503 | apiGroup: rbac.authorization.k8s.io 504 | endsnippet 505 | 506 | snippet rbac "Role and Binding" !b 507 | kind: ${1:Cluster}Role 508 | apiVersion: rbac.authorization.k8s.io/v1 509 | metadata: 510 | ${2:namespace: ${3:default} 511 | }name: ${4:configmap-updater} 512 | rules: 513 | - apiGroups: ["${5:}"] 514 | resources: ["${6:configmaps}"] 515 | resourceNames: ["${7:my-configmap}"] 516 | verbs: [${8:"update", "get"}] 517 | --- 518 | # This role binding allows "jane" to read pods in the "default" namespace. 519 | kind: ${9:Cluster}RoleBinding 520 | apiVersion: rbac.authorization.k8s.io/v1 521 | metadata: 522 | name: $4 523 | $2 524 | subjects: 525 | - kind: ${10:User|ServiceAccount|Group} 526 | name: ${11:jane} # Name is case sensitive 527 | apiGroup: rbac.authorization.k8s.io 528 | roleRef: 529 | kind: $1Role #this must be Role or ClusterRole 530 | name: $4 # this must match the name of the Role or ClusterRole you wish to bind to 531 | apiGroup: rbac.authorization.k8s.io 532 | endsnippet 533 | 534 | snippet hpa "V2 HorizontalPodAutoscaler" !b 535 | apiVersion: autoscaling/v2 536 | kind: HorizontalPodAutoscaler 537 | metadata: 538 | name: ${6:$1} 539 | namespace: ${7:default} 540 | spec: 541 | scaleTargetRef: 542 | apiVersion: apps/v1 543 | kind: Deployment 544 | name: ${1:mydeployment} 545 | minReplicas: ${2:1} 546 | maxReplicas: ${3:5} 547 | metrics: 548 | - resource: 549 | name: memory 550 | target: 551 | averageUtilization: ${4:85} 552 | type: Utilization 553 | type: Resource 554 | - resource: 555 | name: cpu 556 | target: 557 | averageUtilization: ${5:$4} 558 | type: Utilization 559 | type: Resource 560 | endsnippet 561 | 562 | snippet hpa1 "HorizontalPodAutoscaler" !b 563 | apiVersion: autoscaling/v1 564 | kind: HorizontalPodAutoscaler 565 | metadata: 566 | name: ${5:$1} 567 | spec: 568 | scaleTargetRef: 569 | apiVersion: apps/v1 570 | kind: Deployment 571 | name: ${1:mydeployment} 572 | minReplicas: ${2:1} 573 | maxReplicas: ${3:5} 574 | targetCPUUtilizationPercentage: ${4:70} 575 | endsnippet 576 | 577 | snippet pvolm "Volume Mount and spec" !b 578 | volumeMounts: 579 | - name: ${1:volume} 580 | mountPath: ${2:/etc/mount/path} 581 | ${3:subPath: ${4: key}} 582 | volumes: 583 | - name: $1 584 | ${5:configMap}: 585 | ${6:name}: ${7:someName} 586 | endsnippet 587 | 588 | snippet volm "Volume Mount" !b 589 | - name: ${1:volume} 590 | mountPath: ${2:/etc/mount/path} 591 | ${3:subPath: ${4: key}} 592 | $0 593 | endsnippet 594 | 595 | snippet prom "Prometheus annotations" !b 596 | annotations: 597 | prometheus.io/scrape: "true" 598 | prometheus.io/endpoint: "${1:/metrics}" 599 | prometheus.io/port: "${2:8080}" 600 | endsnippet 601 | 602 | snippet ingn "nginx ingress class annotation" !b 603 | kubernetes.io/ingress.class: nginx 604 | endsnippet 605 | 606 | snippet aff "Affinitiy/Anti-Affinity" !b 607 | pod${1:Anti}Affinity: 608 | ${2:preferred|required}DuringSchedulingIgnoredDuringExecution: 609 | - weight: 100 610 | podAffinityTerm: 611 | labelSelector: 612 | matchExpressions: 613 | - key: ${3:app} 614 | operator: In 615 | values: 616 | - ${4:appname} 617 | topologyKey: ${5:kubernetes.io/hostname} 618 | endsnippet 619 | 620 | snippet hpa "Horizontal Pod Autoscaler" !b 621 | apiVersion: autoscaling/v2beta1 622 | kind: HorizontalPodAutoscaler 623 | metadata: 624 | name: ${1:hpa-name} 625 | namespace: ${2:default} 626 | spec: 627 | scaleTargetRef: 628 | apiVersion: apps/v1 629 | kind: Deployment 630 | name: ${3:deployment-name} 631 | minReplicas: 1 632 | maxReplicas: 10 633 | metrics: 634 | - type: Resource 635 | resource: 636 | name: cpu 637 | targetAverageUtilization: 10 638 | - type: Resource 639 | resource: 640 | name: memory 641 | targetAverageValue: 1000Mi 642 | endsnippet 643 | 644 | snippet ds "DaemonSet" !b 645 | apiVersion: apps/v1 646 | kind: DaemonSet 647 | metadata: 648 | name: ${1:name} 649 | namespace: ${2:default} 650 | labels: 651 | app: $1 652 | spec: 653 | selector: 654 | matchLabels: 655 | app: $1 656 | template: 657 | metadata: 658 | labels: 659 | app: $1 660 | spec: 661 | containers: 662 | - name: $1 663 | image: ${3:image/name} 664 | imagePullPolicy: IfNotPresent 665 | resources: 666 | limits: 667 | cpu: 100m 668 | memory: 100Mi 669 | $0 670 | endsnippet 671 | 672 | snippet tol "Toleration" !b 673 | - key: ${1:kubernetes.azure.com/scalesetpriority} 674 | operator: ${2:Equal} 675 | value: ${3:spot} 676 | effect: ${4:NoSchedule} 677 | $0 678 | endsnippet 679 | 680 | snippet meta "Kubernetes metadata header" !b 681 | apiVersion: ${1:apps/v1} 682 | kind: ${2:DaemonSet} 683 | metadata: 684 | name: ${3:name} 685 | namespace: ${4:default} 686 | labels: 687 | app: $3 688 | endsnippet 689 | 690 | snippet cont "Container spec" !b 691 | - name: ${1:alpine} 692 | image: ${2:$1} 693 | imagePullPolicy: Always 694 | resources: 695 | requests: 696 | cpu: ${3:500m} 697 | memory: ${4:512Mi} 698 | limits: 699 | cpu: $3 700 | memory: $4 701 | ports: 702 | - containerPort: ${5:8080} 703 | endsnippet 704 | 705 | snippet secc "SecurityContext" !b 706 | securityContext: 707 | runAsUser: ${1:1000} 708 | runAsGroup: ${2:$1} 709 | fsGroup: ${3:$1}$0 710 | endsnippet 711 | -------------------------------------------------------------------------------- /ftdetect/kubeconf.vim: -------------------------------------------------------------------------------- 1 | " Set filetype for ~/.kube/config 2 | autocmd BufRead,BufNewFile */.kube/config set filetype=yaml 3 | 4 | autocmd BufRead,BufNewFile */templates/*.yaml,*/templates/*.tpl set filetype=yaml.gotexttmpl 5 | 6 | " Detect kubectl get X -oyaml | vim (no file) 7 | function s:DetectKubernetes() abort 8 | if did_filetype() || &ft != '' 9 | return 10 | endif 11 | let l:first_line = getline(1) 12 | if l:first_line =~# '^\(kind\|apiVersion\): ' 13 | set filetype=yaml 14 | endif 15 | endfunction 16 | autocmd BufNewFile,BufRead,BufEnter * call s:DetectKubernetes() 17 | -------------------------------------------------------------------------------- /ftplugin/yaml/kube.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_kube') || &cp || v:version < 700 2 | finish 3 | endif 4 | let g:loaded_kube = 1 5 | 6 | let s:nvim = has('nvim') 7 | let s:async = has('job') && has('channel') 8 | 9 | let s:job_output = {} 10 | 11 | function! kube#decodeSecret() 12 | let input = expand('') 13 | let output = system('base64 -d', input) 14 | exe "norm! ciW " . output . " # decoded" 15 | endfunction 16 | 17 | function! kube#encodeSecret() 18 | let input = expand('') 19 | let output = system('base64 -w0', input) 20 | exe "norm! ciW" . output 21 | endfunction 22 | 23 | function! kube#fileOpDoneCb(ch) 24 | let id = s:ch_get_id(a:ch) 25 | let jo = s:job_output[id] 26 | 27 | let sawError = 0 28 | if ch_status(a:ch, {'part': 'err'}) == 'buffered' 29 | let sawError = 1 30 | while ch_status(a:ch, {'part': 'err'}) == 'buffered' 31 | let line = ch_readraw(a:ch) 32 | call add(jo['lines'], line) 33 | endwhile 34 | else 35 | while ch_status(a:ch, {'part': 'out'}) == 'buffered' 36 | let line = ch_readraw(a:ch) 37 | call add(jo['lines'], line) 38 | endwhile 39 | endif 40 | 41 | let jobOp = jo['op'] 42 | let out = join(jo['lines'], "\n") 43 | 44 | call s:handle_out(out, jobOp, sawError) 45 | endfunction 46 | 47 | function! s:handle_out(out, op, error) 48 | if a:error 49 | echom "Error encountered:\n" . a:out 50 | else 51 | if a:op == 'delete' 52 | echom "Successfully deleted resources:\n" . a:out 53 | else 54 | echom "Successfully applied updates:\n" . a:out 55 | endif 56 | endif 57 | endfunction 58 | 59 | function! s:ch_get_id(ch) 60 | let id = substitute(a:ch, '^channel \(\d\+\) \(open\|closed\)$', '\1', '') 61 | endfunction 62 | 63 | function! s:KubeFileOp(op, wholeDir) range 64 | let cmd = 'kubectl ' . a:op . ' -f ' 65 | 66 | let input = "" 67 | if a:wholeDir 68 | let cmd = cmd . expand('%:h:p') 69 | else 70 | " Using stdin so this can possibly be switched to buffer contents 71 | let cmd = cmd . '-' 72 | let input = join(getline(a:firstline,a:lastline), "\n") 73 | endif 74 | 75 | if a:op == "delete" 76 | let cmd = cmd . " -o name" 77 | endif 78 | 79 | if s:async && (!exists('g:kubernetes_no_async') || !g:kubernetes_no_async) 80 | let job = job_start(cmd, 81 | \{ 82 | \'close_cb': 'kube#fileOpDoneCb', 83 | \'err_io': 'out', 84 | \}) 85 | 86 | let ch = job_getchannel(job) 87 | let id = s:ch_get_id(ch) 88 | 89 | let s:job_output[id] = { 90 | \'lines': [], 91 | \'op': a:op, 92 | \} 93 | 94 | call ch_sendraw(ch, input) 95 | call ch_close_in(ch) 96 | 97 | echom "called " . cmd 98 | else 99 | let out = system(cmd, input) 100 | call s:handle_out(out, a:op, v:shell_error) 101 | endif 102 | endfunction 103 | 104 | fun! s:KubeRecreate() 105 | let g:kubernetes_no_async="true" 106 | call s:KubeFileOp('delete', 0) 107 | unlet g:kubernetes_no_async 108 | call s:KubeFileOp('create', 0) 109 | endf 110 | 111 | command! -range=% KubeApply ,call s:KubeFileOp('apply', 0) 112 | command! KubeDelete call s:KubeFileOp('delete', 0) 113 | command! KubeCreate call s:KubeFileOp('create', 0) 114 | command! KubeRecreate call s:KubeRecreate() 115 | 116 | command! KubeApplyDir call s:KubeFileOp('apply', 1) 117 | command! KubeDeleteDir call s:KubeFileOp('delete', 1) 118 | 119 | command! KubeDecodeSecret call kube#decodeSecret() 120 | command! KubeEncodeSecret call kube#encodeSecret() 121 | --------------------------------------------------------------------------------