diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000..6743d920 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,30 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Unshallow + run: git fetch --prune --unshallow + - + name: Set up Go + uses: actions/setup-go@v1 + with: + go-version: 1.14.x + - + name: GoReleaser Action + uses: goreleaser/goreleaser-action@v1.3.1 + with: + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GORELEASER_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 66fd13c9..35c1f0f1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ *.out # Dependency directories (remove the comment below to include it) -# vendor/ +bin/ + +.DS_Store diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 00000000..cbe5f3cf --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,53 @@ +before: + hooks: + - go mod download + +dist: bin + +builds: +- env: + - CGO_ENABLED=0 + goos: + - darwin + - linux + - windows + goarch: + - amd64 + - 386 + +brews: + - name: lazykubectl + github: + owner: vs4vijay + name: homebrew-tap + url_template: "https://github.com/vs4vijay/lazykubectl/releases/download/{{ .Tag }}/{{ .ArtifactName }}" + commit_author: + name: Vijay Soni (vs4vijay) + email: vs4vijay@gmail.com + folder: Formula + description: "lazykubectl - A better client for kubernetes" + skip_upload: false + install: | + bin.install "lazykubectl" + test: | + system "#{bin}/lazykubectl version" + +archives: +- replacements: + darwin: mac + linux: linux + windows: windows + 386: i386 + amd64: x86_64 + +checksum: + name_template: "checksums.txt" + +snapshot: + name_template: "{{ .Tag }}-next" + +changelog: + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/README.md b/README.md index df540c4b..0a4c1873 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,23 @@ # lazykubectl +--- + +## Screenshots + +![LazyKubectl Pods](_screenshots/lazykubectl_pods.png) + +![LazyKubectl Containers](_screenshots/lazykubectl_containers.png) + +![LazyKubectl Logs](_screenshots/lazykubectl_logs.png) + + --- ## References - https://pkg.go.dev/k8s.io/client-go/kubernetes?tab=doc - https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/ +--- ## ToDo - [x] Auth @@ -55,14 +67,11 @@ clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, &clientcmd.ConfigOverrides{CurrentContext: kubeContext}) - rawConfig, err := clientConfig.RawConfig() +rawConfig, err := clientConfig.RawConfig() rawConfig.CurrentContext - - - go func() { for event := range watch.ResultChan() { fmt.Printf("Type: %v\n", event.Type) @@ -78,97 +87,84 @@ time.Sleep(5 * time.Second) - watch, _ := api.Services("").Watch(metav1.ListOptions{}) - for event := range watch.ResultChan() { - fmt.Printf("Type: %v\n", event.Type) - p, ok := event.Object.(*v1.Pod) - if !ok { - fmt.Errorf("unexpected type") - } - fmt.Println(p.Status.ContainerStatuses) - fmt.Println(p.Status.Phase) +watch, _ := api.Services("").Watch(metav1.ListOptions{}) +for event := range watch.ResultChan() { + fmt.Printf("Type: %v\n", event.Type) + p, ok := event.Object.(*v1.Pod) + if !ok { + fmt.Errorf("unexpected type") } + fmt.Println(p.Status.ContainerStatuses) + fmt.Println(p.Status.Phase) +} watchlist := cache.NewListWatchFromClient(clientset.Core().RESTClient(), "pods", v1.NamespaceDefault, fields.Everything()) - _, controller := cache.NewInformer( - watchlist, - &v1.Pod{}, - time.Second * 0, - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - fmt.Printf("add: %s \n", obj) - }, - DeleteFunc: func(obj interface{}) { - fmt.Printf("delete: %s \n", obj) - }, - UpdateFunc:func(oldObj, newObj interface{}) { - fmt.Printf("old: %s, new: %s \n", oldObj, newObj) - }, +_, controller := cache.NewInformer( + watchlist, + &v1.Pod{}, + time.Second * 0, + cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + fmt.Printf("add: %s \n", obj) }, - ) - stop := make(chan struct{}) - go controller.Run(stop) + DeleteFunc: func(obj interface{}) { + fmt.Printf("delete: %s \n", obj) + }, + UpdateFunc:func(oldObj, newObj interface{}) { + fmt.Printf("old: %s, new: %s \n", oldObj, newObj) + }, + }, +) +stop := make(chan struct{}) +go controller.Run(stop) informer := cache.NewSharedIndexInformer( - &cache.ListWatch{ - ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) { - return kubeClient.CoreV1().Pods(conf.Namespace).List(options) - }, - WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) { - return kubeClient.CoreV1().Pods(conf.Namespace).Watch(options) - }, - }, - &api_v1.Pod{}, - 0, //Skip resync - cache.Indexers{}, - ) - - - - - deploymentsClient := clientset.ExtensionsV1beta1().Deployments("namespace-ffledgling") - - // List existing deployments in namespace - deployments, err := deploymentsClient.List(metav1.ListOptions{}) - - - e.HTTPErrorHandler = func(err error, c echo.Context) { - // Take required information from error and context and send it to a service like New Relic - fmt.Println(c.Path(), c.QueryParams(), err.Error()) - - switch err.(type) { - case orchestrator.CustomError: - fmt.Println("custom") - default: - fmt.Println("normal") // here v has type interface{} - } + &cache.ListWatch{ + ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) { + return kubeClient.CoreV1().Pods(conf.Namespace).List(options) + }, + WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) { + return kubeClient.CoreV1().Pods(conf.Namespace).Watch(options) + }, + }, + &api_v1.Pod{}, + 0, //Skip resync + cache.Indexers{}, + ) - // Call the default handler to return the HTTP response - e.DefaultHTTPErrorHandler(err, c) - } - https://github.com/alitari/kubexp +e.HTTPErrorHandler = func(err error, c echo.Context) { + // Take required information from error and context and send it to a service like New Relic + fmt.Println(c.Path(), c.QueryParams(), err.Error()) - https://github.com/JulienBreux/pody + switch err.(type) { + case orchestrator.CustomError: + fmt.Println("custom") + default: + fmt.Println("normal") // here v has type interface{} + } + // Call the default handler to return the HTTP response + e.DefaultHTTPErrorHandler(err, c) +} - if v, err := g.SetView("help", maxX-25, 0, maxX-1, 9); err != nil { - if err != gocui.ErrUnknownView { - return err - } - fmt.Fprintln(v, "KEYBINDINGS") - fmt.Fprintln(v, "Space: New View") - fmt.Fprintln(v, "Tab: Next View") - fmt.Fprintln(v, "← ↑ → ↓: Move View") - fmt.Fprintln(v, "Backspace: Delete View") - fmt.Fprintln(v, "t: Set view on top") - fmt.Fprintln(v, "b: Set view on bottom") - fmt.Fprintln(v, "^C: Exit") - } +if v, err := g.SetView("help", maxX-25, 0, maxX-1, 9); err != nil { + if err != gocui.ErrUnknownView { + return err + } + fmt.Fprintln(v, "KEYBINDINGS") + fmt.Fprintln(v, "Space: New View") + fmt.Fprintln(v, "Tab: Next View") + fmt.Fprintln(v, "← ↑ → ↓: Move View") + fmt.Fprintln(v, "Backspace: Delete View") + fmt.Fprintln(v, "t: Set view on top") + fmt.Fprintln(v, "b: Set view on bottom") + fmt.Fprintln(v, "^C: Exit") +} func Loader() string { @@ -179,6 +175,10 @@ func Loader() string { return characters[index : index+1] } +https://github.com/alitari/kubexp + +https://github.com/JulienBreux/pody + https://stackoverflow.com/questions/40975307/how-to-watch-events-on-a-kubernetes-service-using-its-go-client https://github.com/NetApp/trident/blob/master/k8s_client/k8s_client.go @@ -197,5 +197,4 @@ NewSharedIndexInformer https://raw.githubusercontent.com/kubernetes/kubernetes/master/hack/testdata/recursive/pod/pod/busybox.yaml https://raw.githubusercontent.com/istio/istio/master/samples/sleep/sleep.yaml - ``` \ No newline at end of file diff --git a/_screenshots/lazykubectl_containers.png b/_screenshots/lazykubectl_containers.png new file mode 100644 index 00000000..8d9d691c Binary files /dev/null and b/_screenshots/lazykubectl_containers.png differ diff --git a/_screenshots/lazykubectl_logs.png b/_screenshots/lazykubectl_logs.png new file mode 100644 index 00000000..65bc4276 Binary files /dev/null and b/_screenshots/lazykubectl_logs.png differ diff --git a/_screenshots/lazykubectl_pods.png b/_screenshots/lazykubectl_pods.png new file mode 100644 index 00000000..733584c7 Binary files /dev/null and b/_screenshots/lazykubectl_pods.png differ diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index f250bf48..3734fba9 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -19,7 +19,7 @@ var ( ) var ( - app *App + app *App // TODO: Not needed here once we move trigger methods to App struct viewSequence = []string{ViewInfo, ViewNamespaces, ViewMain, ViewLogs} activeViewIndex = 0 state = map[string]string{} // TODO: Move this to App struct diff --git a/testdata/admin-role-binding.yaml b/testdata/admin-role-binding.yaml new file mode 100644 index 00000000..cdd56466 --- /dev/null +++ b/testdata/admin-role-binding.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: admin-user + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: admin-user +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: admin-user + namespace: kube-system \ No newline at end of file diff --git a/testdata/busybox.yaml b/testdata/busybox.yaml new file mode 100644 index 00000000..08ff5e77 --- /dev/null +++ b/testdata/busybox.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: busybox1 + labels: + app: busybox1 +spec: + containers: + - image: busybox + command: + - sleep + - "3600" + imagePullPolicy: IfNotPresent + name: busybox + restartPolicy: Always diff --git a/testdata/kube-dashboard.yaml b/testdata/kube-dashboard.yaml new file mode 100644 index 00000000..07cd050e --- /dev/null +++ b/testdata/kube-dashboard.yaml @@ -0,0 +1,302 @@ +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Namespace +metadata: + name: kubernetes-dashboard + +--- + +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard + namespace: kubernetes-dashboard + +--- + +kind: Service +apiVersion: v1 +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard + namespace: kubernetes-dashboard +spec: + ports: + - port: 443 + targetPort: 8443 + selector: + k8s-app: kubernetes-dashboard + +--- + +apiVersion: v1 +kind: Secret +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard-certs + namespace: kubernetes-dashboard +type: Opaque + +--- + +apiVersion: v1 +kind: Secret +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard-csrf + namespace: kubernetes-dashboard +type: Opaque +data: + csrf: "" + +--- + +apiVersion: v1 +kind: Secret +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard-key-holder + namespace: kubernetes-dashboard +type: Opaque + +--- + +kind: ConfigMap +apiVersion: v1 +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard-settings + namespace: kubernetes-dashboard + +--- + +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard + namespace: kubernetes-dashboard +rules: + # Allow Dashboard to get, update and delete Dashboard exclusive secrets. + - apiGroups: [""] + resources: ["secrets"] + resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"] + verbs: ["get", "update", "delete"] + # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map. + - apiGroups: [""] + resources: ["configmaps"] + resourceNames: ["kubernetes-dashboard-settings"] + verbs: ["get", "update"] + # Allow Dashboard to get metrics. + - apiGroups: [""] + resources: ["services"] + resourceNames: ["heapster", "dashboard-metrics-scraper"] + verbs: ["proxy"] + - apiGroups: [""] + resources: ["services/proxy"] + resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"] + verbs: ["get"] + +--- + +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard +rules: + # Allow Metrics Scraper to get metrics from the Metrics server + - apiGroups: ["metrics.k8s.io"] + resources: ["pods", "nodes"] + verbs: ["get", "list", "watch"] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard + namespace: kubernetes-dashboard +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: kubernetes-dashboard +subjects: + - kind: ServiceAccount + name: kubernetes-dashboard + namespace: kubernetes-dashboard + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: kubernetes-dashboard +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: kubernetes-dashboard +subjects: + - kind: ServiceAccount + name: kubernetes-dashboard + namespace: kubernetes-dashboard + +--- + +kind: Deployment +apiVersion: apps/v1 +metadata: + labels: + k8s-app: kubernetes-dashboard + name: kubernetes-dashboard + namespace: kubernetes-dashboard +spec: + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + k8s-app: kubernetes-dashboard + template: + metadata: + labels: + k8s-app: kubernetes-dashboard + spec: + containers: + - name: kubernetes-dashboard + image: kubernetesui/dashboard:v2.0.0-beta8 + imagePullPolicy: Always + ports: + - containerPort: 8443 + protocol: TCP + args: + - --auto-generate-certificates + - --namespace=kubernetes-dashboard + # Uncomment the following line to manually specify Kubernetes API server Host + # If not specified, Dashboard will attempt to auto discover the API server and connect + # to it. Uncomment only if the default does not work. + # - --apiserver-host=http://my-address:port + volumeMounts: + - name: kubernetes-dashboard-certs + mountPath: /certs + # Create on-disk volume to store exec logs + - mountPath: /tmp + name: tmp-volume + livenessProbe: + httpGet: + scheme: HTTPS + path: / + port: 8443 + initialDelaySeconds: 30 + timeoutSeconds: 30 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsUser: 1001 + runAsGroup: 2001 + volumes: + - name: kubernetes-dashboard-certs + secret: + secretName: kubernetes-dashboard-certs + - name: tmp-volume + emptyDir: {} + serviceAccountName: kubernetes-dashboard + nodeSelector: + "beta.kubernetes.io/os": linux + # Comment the following tolerations if Dashboard must not be deployed on master + tolerations: + - key: node-role.kubernetes.io/master + effect: NoSchedule + +--- + +kind: Service +apiVersion: v1 +metadata: + labels: + k8s-app: dashboard-metrics-scraper + name: dashboard-metrics-scraper + namespace: kubernetes-dashboard +spec: + ports: + - port: 8000 + targetPort: 8000 + selector: + k8s-app: dashboard-metrics-scraper + +--- + +kind: Deployment +apiVersion: apps/v1 +metadata: + labels: + k8s-app: dashboard-metrics-scraper + name: dashboard-metrics-scraper + namespace: kubernetes-dashboard +spec: + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + k8s-app: dashboard-metrics-scraper + template: + metadata: + labels: + k8s-app: dashboard-metrics-scraper + annotations: + seccomp.security.alpha.kubernetes.io/pod: 'runtime/default' + spec: + containers: + - name: dashboard-metrics-scraper + image: kubernetesui/metrics-scraper:v1.0.1 + ports: + - containerPort: 8000 + protocol: TCP + livenessProbe: + httpGet: + scheme: HTTP + path: / + port: 8000 + initialDelaySeconds: 30 + timeoutSeconds: 30 + volumeMounts: + - mountPath: /tmp + name: tmp-volume + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsUser: 1001 + runAsGroup: 2001 + serviceAccountName: kubernetes-dashboard + nodeSelector: + "beta.kubernetes.io/os": linux + # Comment the following tolerations if Dashboard must not be deployed on master + tolerations: + - key: node-role.kubernetes.io/master + effect: NoSchedule + volumes: + - name: tmp-volume + emptyDir: {} diff --git a/testdata/sleep.yaml b/testdata/sleep.yaml new file mode 100644 index 00000000..f0728611 --- /dev/null +++ b/testdata/sleep.yaml @@ -0,0 +1,64 @@ +# Copyright 2017 Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################################################################################################## +# Sleep service +################################################################################################## +apiVersion: v1 +kind: ServiceAccount +metadata: + name: sleep +--- +apiVersion: v1 +kind: Service +metadata: + name: sleep + labels: + app: sleep +spec: + ports: + - port: 80 + name: http + selector: + app: sleep +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sleep +spec: + replicas: 1 + selector: + matchLabels: + app: sleep + template: + metadata: + labels: + app: sleep + spec: + serviceAccountName: sleep + containers: + - name: sleep + image: governmentpaas/curl-ssl + command: ["/bin/sleep", "3650d"] + imagePullPolicy: IfNotPresent + volumeMounts: + - mountPath: /etc/sleep/tls + name: secret-volume + volumes: + - name: secret-volume + secret: + secretName: sleep-secret + optional: true +---