init drop
commit
da190c204c
|
|
@ -0,0 +1,10 @@
|
|||
.vscode
|
||||
k9s.log
|
||||
.envrc
|
||||
cov.out
|
||||
execs
|
||||
k9s
|
||||
samples
|
||||
dist
|
||||
notes
|
||||
styles
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
project_name: k9s
|
||||
before:
|
||||
hooks:
|
||||
# you may remove this if you don't use vgo
|
||||
- go mod download
|
||||
# you may remove this if you don't need go generate
|
||||
- go generate ./...
|
||||
|
||||
release:
|
||||
prerelease: true
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
ldflags:
|
||||
- -s -w -X github.com/k8sland/k9s/cmd.version={{.Version}} -X github.com/k8sland/k9s/cmd.commit={{.Commit}} -X github.com/k8sland/k9s/cmd.date={{.Date}}
|
||||
|
||||
archive:
|
||||
replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
|
||||
# Homebrew
|
||||
brew:
|
||||
name: k9s
|
||||
github:
|
||||
owner: k8sland
|
||||
name: k9s-homebrew-tap
|
||||
commit_author:
|
||||
name: k8sland
|
||||
email: fernand@k8sland.io
|
||||
folder: Formula
|
||||
homepage: https://k9ss.io
|
||||
description: A CLI to interact with your Kubernetes clusters
|
||||
test: |
|
||||
system "k9s version"
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
version: v1.0
|
||||
name: First pipeline example
|
||||
agent:
|
||||
machine:
|
||||
type: e1-standard-2
|
||||
os_image: ubuntu1804
|
||||
|
||||
blocks:
|
||||
- name: "Build"
|
||||
task:
|
||||
env_vars:
|
||||
- name: APP_ENV
|
||||
value: prod
|
||||
jobs:
|
||||
- name: Docker build
|
||||
commands:
|
||||
- checkout
|
||||
- ls -1
|
||||
- echo $APP_ENV
|
||||
- echo "Docker build..."
|
||||
- echo "done"
|
||||
|
||||
- name: "Smoke tests"
|
||||
task:
|
||||
jobs:
|
||||
- name: Smoke
|
||||
commands:
|
||||
- checkout
|
||||
- echo "make smoke"
|
||||
|
||||
- name: "Unit tests"
|
||||
task:
|
||||
jobs:
|
||||
- name: RSpec
|
||||
commands:
|
||||
- checkout
|
||||
- echo "make rspec"
|
||||
|
||||
- name: Lint code
|
||||
commands:
|
||||
- checkout
|
||||
- echo "make lint"
|
||||
|
||||
- name: Check security
|
||||
commands:
|
||||
- checkout
|
||||
- echo "make security"
|
||||
|
||||
- name: "Integration tests"
|
||||
task:
|
||||
jobs:
|
||||
- name: Cucumber
|
||||
commands:
|
||||
- checkout
|
||||
- echo "make cucumber"
|
||||
|
||||
- name: "Push Image"
|
||||
task:
|
||||
jobs:
|
||||
- name: Push
|
||||
commands:
|
||||
- checkout
|
||||
- echo "make docker.push"
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
language: go
|
||||
go_import_path: github.com/k8sland/k9s
|
||||
go:
|
||||
- 1.11.2
|
||||
# - master
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
install: true
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
script:
|
||||
- go build
|
||||
- go test ./...
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
Copyright © 2018, Imhotep Software LLC <fernand@k8sland.io>
|
||||
All rights reserved.
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
NAME := k9s
|
||||
PACKAGE := github.com/k8sland/$(NAME)
|
||||
VERSION := $(shell git rev-parse --short HEAD)
|
||||
|
||||
default: help
|
||||
|
||||
cover: ## Run test coverage suite
|
||||
@go test ./... --coverprofile=cov.out
|
||||
@go tool cover --html=cov.out
|
||||
|
||||
osx: ## Builds OSX CLI
|
||||
@env GOOS=darwin GOARCH=amd64 go build \
|
||||
-ldflags "-w -X ${PACKAGE}/cmd.Version=${VERSION}" \
|
||||
-a -tags netgo -o execs/${NAME} *.go
|
||||
|
||||
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[38;5;69m%-30s\033[38;5;168m %s\n", $$1, $$2}'
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
<img src="assets/logo_w.png">
|
||||
|
||||
# K9s - Kubernetes Screens
|
||||
|
||||
A Kubernetes CLI written in GO and curses to interact with your clusters. The initial
|
||||
aim of this project is to make it simpler to navigate and diagnose a cluster.
|
||||
|
||||
<br/>
|
||||
|
||||
---
|
||||
|
||||
[](https://goreportcard.com/report/github.com/k8sland/k9s)
|
||||
[](https://travis-ci.org/k8sland/k9s)
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
---
|
||||
## Description
|
||||
|
||||
K9s is a CLI for Kubernetes. It provides a bit more information about your cluster
|
||||
than *kubectl* while allowing to perform primordial Kubernetes commands with
|
||||
ease.
|
||||
|
||||
At the time of this writing, K9s only supports a subset of all available Kubernetes
|
||||
resources. More will be added soon (please PR us to add your favorite resource!)
|
||||
|
||||
<br/>
|
||||
|
||||
---
|
||||
## Installation
|
||||
|
||||
### Homebrew (OSX)
|
||||
|
||||
```shell
|
||||
brew tap k8sland/k9s https://github.com/k8sland/k9s-homebrew-tap.git
|
||||
brew install k9s
|
||||
```
|
||||
|
||||
### Binary Releases
|
||||
|
||||
- [Releases](https://github.com/k8sland/k9s/releases)
|
||||
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
---
|
||||
## Features
|
||||
|
||||
> Note: K9s does not have an idiot light. Please be sure to hit the correct command
|
||||
> sequences to avoid pilot errors. `Are you sure?` not in effect here...
|
||||
|
||||
+ K9s uses 2 or 3 letters alias to navigate to a K8s resource
|
||||
+ At any time you can use `?<Enter>` to look up the various commands
|
||||
+ Use `alias<Enter>` to activate a resource under that alias
|
||||
+ Use `Esc` to erase previous keystrokes.
|
||||
+ Use `Q` or `Ctrl-C` to Quit.
|
||||
+ `Ctrl` sequences are used to view, edit, delete, ssh ...
|
||||
+ Use `ctx<Enter>` to see and switch between your clusters
|
||||
|
||||
<br/>
|
||||
|
||||
---
|
||||
## Video Demo
|
||||
|
||||
+ [K9s Demo](https://youtu.be/k7zseUhaXeU)
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
---
|
||||
## Screen Shots
|
||||
|
||||
### Pod View
|
||||
|
||||
<img src="assets/screen_1.png">
|
||||
|
||||
### Log View
|
||||
|
||||
<img src="assets/screen_2.png">
|
||||
|
||||
<br/>
|
||||
|
||||
---
|
||||
## Known Issues...
|
||||
|
||||
This initial drop is brittle. K9s will most likely blow up if...
|
||||
|
||||
+ Your kube-config file does not live under $HOME/.kube or you use multiple configs
|
||||
+ You don't have enough RBAC fu to manage your cluster
|
||||
+ Your cluster does not run a metrics-server
|
||||
+ You have more than 9 namespaces
|
||||
+ Most likely will bork on older Kubernetes revs. Guessing > 1.9+ is Ok??
|
||||
+ Not sure at this time about the ill effects for large clusters??
|
||||
+ Many others for sure...
|
||||
|
||||
<br/>
|
||||
|
||||
---
|
||||
## Disclaimer
|
||||
|
||||
This is still work in progress! If there is enough interest in the Kubernetes
|
||||
community, we will enhance per your recommendations/contributions. Also if you
|
||||
dig this effort, please let us know that too!
|
||||
|
||||
<br/>
|
||||
|
||||
---
|
||||
## ATTA Girls/Boys!
|
||||
|
||||
K9s sits on top of two very cool GO projects that provides the much needed terminal
|
||||
support. So big thanks and shootout to the good folks at tcell+tview for
|
||||
making K9s a reality!!
|
||||
|
||||
+ [tcell](https://github.com/gdamore/tcell)
|
||||
+ [tview](https://github.com/rivo/tview)
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
---
|
||||
## Contact Information
|
||||
|
||||
+ **Email**: fernand@k8sland.io
|
||||
+ **Twitter**: [@kitesurfer](https://twitter.com/kitesurfer?lang=en)
|
||||
+ **Github**: [K9s](https://github.com/k8sland/k9s)
|
||||
<br/>
|
||||
|
||||
---
|
||||
<img src="assets/imhotep_logo.png" width="32" height="auto"/> © 2018 Imhotep Software LLC.
|
||||
All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 410 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 330 KiB |
|
|
@ -0,0 +1,124 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
|
||||
"github.com/k8sland/k9s/views"
|
||||
"github.com/k8sland/tview"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRefreshRate = 5 // secs
|
||||
)
|
||||
|
||||
var (
|
||||
version = "dev"
|
||||
commit = "dev"
|
||||
date = "n/a"
|
||||
srv kubernetes.Interface
|
||||
refreshRate int
|
||||
namespace string
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "k9s",
|
||||
Short: "K9s a Kubernetes cluster management CLI",
|
||||
Long: `K9s is a Kubernetes CLI to view and manage your Kubernetes clusters.`,
|
||||
Run: run,
|
||||
}
|
||||
versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print K9s version",
|
||||
Long: "Prints K9s version",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Version:%s GitCommit:%s On %s\n", version, commit, date)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
|
||||
rootCmd.Flags().IntVarP(
|
||||
&refreshRate,
|
||||
"refresh", "r",
|
||||
defaultRefreshRate,
|
||||
"Specifies the default refresh rate as an integer (sec)",
|
||||
)
|
||||
|
||||
rootCmd.Flags().StringVarP(
|
||||
&namespace,
|
||||
"namespace", "n",
|
||||
"",
|
||||
"Uses a given namespace versus all-namespaces",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
// Execute root command
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(cmd *cobra.Command, args []string) {
|
||||
initStyles()
|
||||
initKeys()
|
||||
|
||||
if refreshRate < 0 {
|
||||
refreshRate = defaultRefreshRate
|
||||
}
|
||||
app := views.NewApp(version, refreshRate, namespace)
|
||||
app.Init()
|
||||
app.Run()
|
||||
}
|
||||
|
||||
func initKeys() {
|
||||
tcell.KeyNames[tcell.Key(views.Key0)] = "0"
|
||||
tcell.KeyNames[tcell.Key(views.Key1)] = "1"
|
||||
tcell.KeyNames[tcell.Key(views.Key2)] = "2"
|
||||
tcell.KeyNames[tcell.Key(views.Key3)] = "3"
|
||||
tcell.KeyNames[tcell.Key(views.Key4)] = "4"
|
||||
tcell.KeyNames[tcell.Key(views.Key5)] = "5"
|
||||
tcell.KeyNames[tcell.Key(views.Key6)] = "6"
|
||||
tcell.KeyNames[tcell.Key(views.Key7)] = "7"
|
||||
tcell.KeyNames[tcell.Key(views.Key8)] = "8"
|
||||
tcell.KeyNames[tcell.Key(views.Key9)] = "9"
|
||||
tcell.KeyNames[tcell.Key(views.KeyA)] = "a"
|
||||
tcell.KeyNames[tcell.Key(views.KeyB)] = "b"
|
||||
tcell.KeyNames[tcell.Key(views.KeyC)] = "c"
|
||||
tcell.KeyNames[tcell.Key(views.KeyD)] = "d"
|
||||
tcell.KeyNames[tcell.Key(views.KeyE)] = "e"
|
||||
tcell.KeyNames[tcell.Key(views.KeyF)] = "f"
|
||||
tcell.KeyNames[tcell.Key(views.KeyG)] = "g"
|
||||
tcell.KeyNames[tcell.Key(views.KeyH)] = "h"
|
||||
tcell.KeyNames[tcell.Key(views.KeyI)] = "i"
|
||||
tcell.KeyNames[tcell.Key(views.KeyJ)] = "j"
|
||||
tcell.KeyNames[tcell.Key(views.KeyK)] = "k"
|
||||
tcell.KeyNames[tcell.Key(views.KeyL)] = "l"
|
||||
tcell.KeyNames[tcell.Key(views.KeyM)] = "m"
|
||||
tcell.KeyNames[tcell.Key(views.KeyN)] = "n"
|
||||
tcell.KeyNames[tcell.Key(views.KeyO)] = "o"
|
||||
tcell.KeyNames[tcell.Key(views.KeyP)] = "p"
|
||||
tcell.KeyNames[tcell.Key(views.KeyQ)] = "q"
|
||||
tcell.KeyNames[tcell.Key(views.KeyR)] = "r"
|
||||
tcell.KeyNames[tcell.Key(views.KeyS)] = "s"
|
||||
tcell.KeyNames[tcell.Key(views.KeyT)] = "t"
|
||||
tcell.KeyNames[tcell.Key(views.KeyU)] = "u"
|
||||
tcell.KeyNames[tcell.Key(views.KeyV)] = "v"
|
||||
tcell.KeyNames[tcell.Key(views.KeyX)] = "x"
|
||||
tcell.KeyNames[tcell.Key(views.KeyY)] = "y"
|
||||
tcell.KeyNames[tcell.Key(views.KeyZ)] = "z"
|
||||
}
|
||||
|
||||
func initStyles() {
|
||||
tview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack
|
||||
tview.Styles.ContrastBackgroundColor = tcell.ColorBlack
|
||||
tview.Styles.FocusColor = tcell.ColorLightSkyBlue
|
||||
tview.Styles.BorderColor = tcell.ColorDodgerBlue
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
module github.com/k8sland/k9s
|
||||
|
||||
// replace github.com/k8sland/tview => /Users/fernand/k8sland/src/github.com/k8sland/tview
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.34.0 // indirect
|
||||
github.com/gdamore/tcell v1.1.0
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/gogo/protobuf v1.1.1 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
|
||||
github.com/googleapis/gnostic v0.2.0 // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||
github.com/imdario/mergo v0.3.6 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.5 // indirect
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/k8sland/tview v0.1.0
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/onsi/gomega v1.4.3 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/petergtz/pegomock v0.0.0-20181206220228-b113d17a7e81
|
||||
github.com/sirupsen/logrus v1.2.0
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/pflag v1.0.3 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd // indirect
|
||||
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20181105165119-ca4130e427c7 // indirect
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
||||
golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc // indirect
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
|
||||
google.golang.org/appengine v1.3.0 // indirect
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
k8s.io/api v0.0.0-20181102122915-de5c567eef5c
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20181206111255-bb0a52a3f19d // indirect
|
||||
k8s.io/apimachinery v0.0.0-20181108045954-261df694e725
|
||||
k8s.io/client-go v9.0.0+incompatible
|
||||
k8s.io/kubernetes v1.13.0
|
||||
k8s.io/metrics v0.0.0-20181121073115-d8618695b08f
|
||||
)
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 h1:hheUEMzaOie/wKeIc1WPa7CDVuIO5hqQxjS+dwTQEnI=
|
||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ=
|
||||
github.com/gdamore/tcell v1.1.0 h1:RbQgl7jukmdqROeNcKps7R2YfDCQbWkOd1BwdXrxfr4=
|
||||
github.com/gdamore/tcell v1.1.0/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
|
||||
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/k8sland/tview v0.1.0 h1:nXX/f55woLQvuXtz+7bMhfY+jMtJIrRRiKSOSH357iE=
|
||||
github.com/k8sland/tview v0.1.0/go.mod h1:5qZeY4qT3maFEq50mfj5xBnFvLNoUtl7NyyW48XxmBs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/petergtz/pegomock v0.0.0-20181206220228-b113d17a7e81 h1:MhSbvsIs4KvpPYr4taOvb6j+r9VNbj/08AfjsKi+Ui0=
|
||||
github.com/petergtz/pegomock v0.0.0-20181206220228-b113d17a7e81/go.mod h1:nuBLWZpVyv/fLo56qTwt/AUau7jgouO1h7bEvZCq82o=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd h1:VtIkGDhk0ph3t+THbvXHfMZ8QHgsBO39Nh52+74pq7w=
|
||||
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc h1:ZMCWScCvS2fUVFw8LOpxyUUW5qiviqr4Dg5NdjLeiLU=
|
||||
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20181105165119-ca4130e427c7 h1:g9UOdtsRWEwHYUG2bDHMxKrvfSGE5epIX2HkaMHSMBY=
|
||||
golang.org/x/oauth2 v0.0.0-20181105165119-ca4130e427c7/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc h1:SdCq5U4J+PpbSDIl9bM0V1e1Ug1jsnBkAFvTs1htn7U=
|
||||
golang.org/x/sys v0.0.0-20181031143558-9b800f95dbbc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY=
|
||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
k8s.io/api v0.0.0-20181102122915-de5c567eef5c h1:zyOnDtUzVKOq6N8zrpdzocoytjSxznRy0TmfKDahC20=
|
||||
k8s.io/api v0.0.0-20181102122915-de5c567eef5c/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20181206111255-bb0a52a3f19d h1:Be0abb3DYtNLaSVW4xCGGBSQqtT7myqj1coR6Bu2ZM8=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20181206111255-bb0a52a3f19d/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
|
||||
k8s.io/apimachinery v0.0.0-20181108045954-261df694e725 h1:b4fe6FhSyMdpi6WNeCxxr+kKM8Ya4TaKxeXkpWwh594=
|
||||
k8s.io/apimachinery v0.0.0-20181108045954-261df694e725/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
|
||||
k8s.io/client-go v9.0.0+incompatible h1:2kqW3X2xQ9SbFvWZjGEHBLlWc1LG9JIJNXWkuqwdZ3A=
|
||||
k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
|
||||
k8s.io/kubernetes v1.13.0 h1:2psb4AOWOU3rESSjRVkqHRIIXkqLppfeiR6YE0trpt0=
|
||||
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
||||
k8s.io/metrics v0.0.0-20181121073115-d8618695b08f h1:HyUoIBzks9xTaSnMJ6kv/SSmwaQQccokuiriu2cV0aA=
|
||||
k8s.io/metrics v0.0.0-20181121073115-d8618695b08f/go.mod h1:a25VAbm3QT3xiVl1jtoF1ueAKQM149UdZ+L93ePfV3M=
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/k8sland/k9s/cmd"
|
||||
log "github.com/sirupsen/logrus"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
file, err := os.OpenFile(path.Join("/tmp", "k9s.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.SetOutput(file)
|
||||
}
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type (
|
||||
// Creator can create a new resources.
|
||||
Creator interface {
|
||||
NewInstance(interface{}) Columnar
|
||||
}
|
||||
|
||||
// Caller can call Kubernetes verbs on a resource.
|
||||
Caller interface {
|
||||
k8s.Res
|
||||
}
|
||||
|
||||
// APIFn knows how to call K8s api server.
|
||||
APIFn func() k8s.Res
|
||||
|
||||
// InstanceFn instantiates a concrete resource.
|
||||
InstanceFn func(interface{}) Columnar
|
||||
|
||||
// Base resource.
|
||||
Base struct {
|
||||
path string
|
||||
|
||||
caller Caller
|
||||
creator Creator
|
||||
}
|
||||
)
|
||||
|
||||
// Name returns the resource namespaced name.
|
||||
func (b *Base) Name() string {
|
||||
return b.path
|
||||
}
|
||||
|
||||
// Get a resource by name
|
||||
func (b *Base) Get(path string) (Columnar, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := b.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.creator.NewInstance(i), nil
|
||||
}
|
||||
|
||||
// List all resources
|
||||
func (b *Base) List(ns string) (Columnars, error) {
|
||||
ii, err := b.caller.List(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc := make(Columnars, 0, len(ii))
|
||||
for i := 0; i < len(ii); i++ {
|
||||
cc = append(cc, b.creator.NewInstance(ii[i]))
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a resource by name.
|
||||
func (b *Base) Delete(path string) error {
|
||||
ns, n := namespaced(path)
|
||||
return b.caller.Delete(ns, n)
|
||||
}
|
||||
|
||||
func (*Base) namespacedName(m metav1.ObjectMeta) string {
|
||||
return path.Join(m.Namespace, m.Name)
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
type (
|
||||
// ClusterIfc represents a cluster.
|
||||
ClusterIfc interface {
|
||||
Version() (string, error)
|
||||
ClusterName() string
|
||||
}
|
||||
|
||||
// MetricsIfc represents a metrics server.
|
||||
MetricsIfc interface {
|
||||
NodeMetrics() (k8s.Metric, error)
|
||||
PerNodeMetrics([]v1.Node) (map[string]k8s.Metric, error)
|
||||
PodMetrics() (map[string]k8s.Metric, error)
|
||||
}
|
||||
|
||||
// Cluster represents a kubernetes resource.
|
||||
Cluster struct {
|
||||
api ClusterIfc
|
||||
mx MetricsIfc
|
||||
}
|
||||
)
|
||||
|
||||
// NewCluster returns a new cluster info resource.
|
||||
func NewCluster() *Cluster {
|
||||
return NewClusterWithArgs(k8s.NewCluster(), k8s.NewMetricsServer())
|
||||
}
|
||||
|
||||
// NewClusterWithArgs for tests only!
|
||||
func NewClusterWithArgs(ci ClusterIfc, mx MetricsIfc) *Cluster {
|
||||
return &Cluster{api: ci, mx: mx}
|
||||
}
|
||||
|
||||
// Version returns the current K8s cluster version.
|
||||
func (c *Cluster) Version() string {
|
||||
info, err := c.api.Version()
|
||||
if err != nil {
|
||||
return "n/a"
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// Name returns the cluster name
|
||||
func (c *Cluster) Name() string {
|
||||
return c.api.ClusterName()
|
||||
}
|
||||
|
||||
// Metrics gathers node level metrics and compute utilization percentages.
|
||||
func (c *Cluster) Metrics() (k8s.Metric, error) {
|
||||
return c.mx.NodeMetrics()
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClusterVersion(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
cIfc, mxIfc := NewMockClusterIfc(), NewMockMetricsIfc()
|
||||
m.When(cIfc.Version()).ThenReturn("1.2.3", nil)
|
||||
|
||||
ci := resource.NewClusterWithArgs(cIfc, mxIfc)
|
||||
assert.Equal(t, "1.2.3", ci.Version())
|
||||
}
|
||||
|
||||
func TestClusterNoVersion(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
cIfc, mxIfc := NewMockClusterIfc(), NewMockMetricsIfc()
|
||||
m.When(cIfc.Version()).ThenReturn("bad", fmt.Errorf("No data"))
|
||||
|
||||
ci := resource.NewClusterWithArgs(cIfc, mxIfc)
|
||||
assert.Equal(t, "n/a", ci.Version())
|
||||
}
|
||||
|
||||
func TestClusterName(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
cIfc, mxIfc := NewMockClusterIfc(), NewMockMetricsIfc()
|
||||
m.When(cIfc.ClusterName()).ThenReturn("fred")
|
||||
|
||||
ci := resource.NewClusterWithArgs(cIfc, mxIfc)
|
||||
assert.Equal(t, "fred", ci.Name())
|
||||
}
|
||||
|
||||
func TestClusterMetrics(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
cIfc, mxIfc := NewMockClusterIfc(), NewMockMetricsIfc()
|
||||
m.When(mxIfc.NodeMetrics()).ThenReturn(testMetric(), nil)
|
||||
|
||||
c := resource.NewClusterWithArgs(cIfc, mxIfc)
|
||||
m, err := c.Metrics()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, testMetric(), m)
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func setup(t *testing.T) {
|
||||
m.RegisterMockTestingT(t)
|
||||
m.RegisterMockFailHandler(func(m string, i ...int) {
|
||||
log.Println("Boom!", m, i)
|
||||
})
|
||||
}
|
||||
|
||||
func testMetric() k8s.Metric {
|
||||
return k8s.Metric{
|
||||
CPU: "100m",
|
||||
AvailCPU: "1000m",
|
||||
Mem: "256Gi",
|
||||
AvailMem: "512Gi",
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ConfigMap tracks a kubernetes resource.
|
||||
type ConfigMap struct {
|
||||
*Base
|
||||
instance *v1.ConfigMap
|
||||
}
|
||||
|
||||
// NewConfigMapList returns a new resource list.
|
||||
func NewConfigMapList(ns string) List {
|
||||
return NewConfigMapListWithArgs(ns, NewConfigMap())
|
||||
}
|
||||
|
||||
// NewConfigMapListWithArgs returns a new resource list.
|
||||
func NewConfigMapListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "cm", res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewConfigMap instantiates a new ConfigMap.
|
||||
func NewConfigMap() *ConfigMap {
|
||||
return NewConfigMapWithArgs(k8s.NewConfigMap())
|
||||
}
|
||||
|
||||
// NewConfigMapWithArgs instantiates a new ConfigMap.
|
||||
func NewConfigMapWithArgs(r k8s.Res) *ConfigMap {
|
||||
cm := &ConfigMap{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
cm.creator = cm
|
||||
return cm
|
||||
}
|
||||
|
||||
// NewInstance builds a new ConfigMap instance from a k8s resource.
|
||||
func (*ConfigMap) NewInstance(i interface{}) Columnar {
|
||||
cm := NewConfigMap()
|
||||
switch i.(type) {
|
||||
case *v1.ConfigMap:
|
||||
cm.instance = i.(*v1.ConfigMap)
|
||||
case v1.ConfigMap:
|
||||
ii := i.(v1.ConfigMap)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *ConfigMap) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cm := i.(*v1.ConfigMap)
|
||||
cm.TypeMeta.APIVersion = "v1"
|
||||
cm.TypeMeta.Kind = "ConfigMap"
|
||||
raw, err := yaml.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*ConfigMap) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh, "NAME", "DATA", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *ConfigMap) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
strconv.Itoa(len(i.Data)),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*ConfigMap) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestCMHeader(t *testing.T) {
|
||||
assert.Equal(t,
|
||||
resource.Row{"NAME", "DATA", "AGE"},
|
||||
newConfigMap().Header("default"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestCMHeaderAllNS(t *testing.T) {
|
||||
assert.Equal(t,
|
||||
resource.Row{"NAMESPACE", "NAME", "DATA", "AGE"},
|
||||
newConfigMap().Header(resource.AllNamespaces),
|
||||
)
|
||||
}
|
||||
|
||||
func TestCMFieldsAllNS(t *testing.T) {
|
||||
r := newConfigMap().Fields(resource.AllNamespaces)
|
||||
assert.Equal(t, "blee", r[0])
|
||||
assert.Equal(t, "fred", r[1])
|
||||
assert.Equal(t, "2", r[2])
|
||||
}
|
||||
|
||||
func TestCMFields(t *testing.T) {
|
||||
r := newConfigMap().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
assert.Equal(t, "2", r[1])
|
||||
}
|
||||
|
||||
func TestCMGet(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sCM(), nil)
|
||||
|
||||
cm := resource.NewConfigMapWithArgs(ca)
|
||||
ma, err := cm.Get("blee/fred")
|
||||
assert.Nil(t, err)
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Equal(t, cm.NewInstance(k8sCM()), ma)
|
||||
}
|
||||
|
||||
func TestCMList(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List("blee")).ThenReturn(k8s.Collection{*k8sCM()}, nil)
|
||||
|
||||
cm := resource.NewConfigMapWithArgs(ca)
|
||||
ma, err := cm.List("blee")
|
||||
assert.Nil(t, err)
|
||||
ca.VerifyWasCalledOnce().List("blee")
|
||||
assert.Equal(t, resource.Columnars{cm.NewInstance(k8sCM())}, ma)
|
||||
}
|
||||
|
||||
func TestCMDelete(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Delete("blee", "fred")).ThenReturn(nil)
|
||||
|
||||
cm := resource.NewConfigMapWithArgs(ca)
|
||||
assert.Nil(t, cm.Delete("blee/fred"))
|
||||
ca.VerifyWasCalledOnce().Delete("blee", "fred")
|
||||
}
|
||||
|
||||
func TestCMMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sCM(), nil)
|
||||
|
||||
cm := resource.NewConfigMapWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, cmYaml(), ma)
|
||||
}
|
||||
|
||||
func TestCMListSort(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca))
|
||||
kk := []string{"c", "b", "a"}
|
||||
l.SortFn()(kk)
|
||||
assert.Equal(t, []string{"a", "b", "c"}, kk)
|
||||
}
|
||||
|
||||
func TestCMListHasName(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca))
|
||||
assert.Equal(t, "cm", l.GetName())
|
||||
}
|
||||
|
||||
func TestCMListHasNamespace(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca))
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
}
|
||||
|
||||
func TestCMListHasResource(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca))
|
||||
assert.NotNil(t, l.Resource())
|
||||
}
|
||||
|
||||
func TestCMListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sCM(), nil)
|
||||
|
||||
l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
func TestCMListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List("blee")).ThenReturn(k8s.Collection{*k8sCM()}, nil)
|
||||
|
||||
l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List("blee")
|
||||
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 3, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred", "2"}, row.Fields[:2])
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func newConfigMap() resource.Columnar {
|
||||
return resource.NewConfigMap().NewInstance(k8sCM())
|
||||
}
|
||||
|
||||
func k8sCM() *v1.ConfigMap {
|
||||
return &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "fred",
|
||||
Namespace: "blee",
|
||||
CreationTimestamp: metav1.Time{testTime()},
|
||||
},
|
||||
Data: map[string]string{"blee": "blee", "duh": "duh"},
|
||||
}
|
||||
}
|
||||
|
||||
func cmYaml() string {
|
||||
return `typemeta:
|
||||
kind: ConfigMap
|
||||
apiversion: v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
data:
|
||||
blee: blee
|
||||
duh: duh
|
||||
binarydata: {}
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SwitchableRes represents a resource that can be switched.
|
||||
type SwitchableRes interface {
|
||||
k8s.ContextRes
|
||||
}
|
||||
|
||||
// Context tracks a kubernetes resource.
|
||||
type Context struct {
|
||||
*Base
|
||||
instance *k8s.NamedContext
|
||||
}
|
||||
|
||||
// NewContextList returns a new resource list.
|
||||
func NewContextList(ns string) List {
|
||||
return NewContextListWithArgs(ns, NewContext())
|
||||
}
|
||||
|
||||
// NewContextListWithArgs returns a new resource list.
|
||||
func NewContextListWithArgs(ns string, res Resource) List {
|
||||
return newList(NotNamespaced, "ctx", res, SwitchAccess|DeleteAccess)
|
||||
}
|
||||
|
||||
// NewContext instantiates a new Context.
|
||||
func NewContext() *Context {
|
||||
return NewContextWithArgs(k8s.NewContext().(SwitchableRes))
|
||||
}
|
||||
|
||||
// NewContextWithArgs instantiates a new Context.
|
||||
func NewContextWithArgs(r SwitchableRes) *Context {
|
||||
ctx := &Context{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ctx.creator = ctx
|
||||
return ctx
|
||||
}
|
||||
|
||||
// NewInstance builds a new Context instance from a k8s resource.
|
||||
func (r *Context) NewInstance(i interface{}) Columnar {
|
||||
c := NewContext()
|
||||
switch i.(type) {
|
||||
case *k8s.NamedContext:
|
||||
c.instance = i.(*k8s.NamedContext)
|
||||
case k8s.NamedContext:
|
||||
ii := i.(k8s.NamedContext)
|
||||
c.instance = &ii
|
||||
default:
|
||||
log.Fatalf("unknown context type %#v", i)
|
||||
}
|
||||
c.path = c.instance.Name
|
||||
return c
|
||||
}
|
||||
|
||||
// Switch out current context.
|
||||
func (r *Context) Switch(c string) error {
|
||||
return r.caller.(k8s.ContextRes).Switch(c)
|
||||
}
|
||||
|
||||
// Marshal the resource to yaml.
|
||||
func (r *Context) Marshal(path string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*Context) Header(string) Row {
|
||||
return append(Row{}, "NAME", "CLUSTER", "AUTHINFO", "NAMESPACE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *Context) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
|
||||
name := i.Name
|
||||
if i.CurrentCluster() == name {
|
||||
name += "*"
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
name,
|
||||
i.Context.Cluster,
|
||||
i.Context.AuthInfo,
|
||||
i.Context.Namespace,
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*Context) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
pegomock "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
func TestCTXHeader(t *testing.T) {
|
||||
assert.Equal(t,
|
||||
resource.Row{"NAME", "CLUSTER", "AUTHINFO", "NAMESPACE"},
|
||||
newContext().Header(""),
|
||||
)
|
||||
}
|
||||
|
||||
func TestCTXFieldsAllNS(t *testing.T) {
|
||||
r := newContext().Fields(resource.AllNamespaces)
|
||||
assert.Equal(t, "test", r[0])
|
||||
assert.Equal(t, "blee", r[1])
|
||||
assert.Equal(t, "secret", r[2])
|
||||
}
|
||||
|
||||
func TestCTXSwitch(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockSwitchableRes()
|
||||
m.When(ca.Switch("fred")).ThenReturn(nil)
|
||||
|
||||
ctx := resource.NewContextWithArgs(ca)
|
||||
err := ctx.Switch("fred")
|
||||
assert.Nil(t, err)
|
||||
ca.VerifyWasCalledOnce().Switch("fred")
|
||||
}
|
||||
|
||||
func TestCTXList(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockSwitchableRes()
|
||||
m.When(ca.List("blee")).ThenReturn(k8s.Collection{*k8sNamedCTX()}, nil)
|
||||
|
||||
ctx := resource.NewContextWithArgs(ca)
|
||||
cc, err := ctx.List("blee")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, resource.Columnars{ctx.NewInstance(k8sNamedCTX())}, cc)
|
||||
ca.VerifyWasCalledOnce().List("blee")
|
||||
}
|
||||
func TestCTXDelete(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockSwitchableRes()
|
||||
m.When(ca.Delete("", "fred")).ThenReturn(nil)
|
||||
|
||||
cm := resource.NewContextWithArgs(ca)
|
||||
assert.Nil(t, cm.Delete("fred"))
|
||||
ca.VerifyWasCalledOnce().Delete("", "fred")
|
||||
}
|
||||
|
||||
func TestCTXListSort(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockSwitchableRes()
|
||||
l := resource.NewContextListWithArgs("blee", resource.NewContextWithArgs(ca))
|
||||
kk := []string{"c", "b", "a"}
|
||||
l.SortFn()(kk)
|
||||
assert.Equal(t, []string{"a", "b", "c"}, kk)
|
||||
}
|
||||
|
||||
func TestCTXListHasName(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockSwitchableRes()
|
||||
m.When(ca.List("blee")).ThenReturn(k8s.Collection{*k8sNamedCTX()}, nil)
|
||||
|
||||
l := resource.NewContextListWithArgs("blee", resource.NewContextWithArgs(ca))
|
||||
assert.Equal(t, "ctx", l.GetName())
|
||||
}
|
||||
|
||||
func TestCTXListHasNamespace(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockSwitchableRes()
|
||||
l := resource.NewContextListWithArgs("blee", resource.NewContextWithArgs(ca))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
}
|
||||
|
||||
func TestCTXListHasResource(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockSwitchableRes()
|
||||
l := resource.NewContextListWithArgs("blee", resource.NewContextWithArgs(ca))
|
||||
assert.NotNil(t, l.Resource())
|
||||
}
|
||||
|
||||
func TestCTXListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockSwitchableRes()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sNamedCTX(), nil)
|
||||
|
||||
l := resource.NewContextListWithArgs("blee", resource.NewContextWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
}
|
||||
|
||||
func TestCTXListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockSwitchableRes()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sNamedCTX()}, nil)
|
||||
|
||||
l := resource.NewContextListWithArgs("blee", resource.NewContextWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
assert.Nil(t, l.Reconcile())
|
||||
}
|
||||
ca.VerifyWasCalled(pegomock.Times(2)).List(resource.NotNamespaced)
|
||||
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["test"]
|
||||
assert.Equal(t, 4, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"test", "blee", "secret", ""}, row.Fields)
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func newContext() resource.Columnar {
|
||||
return resource.NewContext().NewInstance(k8sNamedCTX())
|
||||
}
|
||||
|
||||
func k8sCTX() *api.Context {
|
||||
return &api.Context{
|
||||
LocationOfOrigin: "fred",
|
||||
Cluster: "blee",
|
||||
AuthInfo: "secret",
|
||||
}
|
||||
}
|
||||
|
||||
func k8sNamedCTX() *k8s.NamedContext {
|
||||
ctx := k8s.NamedContext{
|
||||
Name: "test",
|
||||
Context: &api.Context{
|
||||
LocationOfOrigin: "fred",
|
||||
Cluster: "blee",
|
||||
AuthInfo: "secret",
|
||||
},
|
||||
}
|
||||
return &ctx
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/api/rbac/v1"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// ClusterRole tracks a kubernetes resource.
|
||||
type ClusterRole struct {
|
||||
*Base
|
||||
instance *v1.ClusterRole
|
||||
}
|
||||
|
||||
// NewClusterRoleList returns a new resource list.
|
||||
func NewClusterRoleList(ns string) List {
|
||||
return NewClusterRoleListWithArgs(ns, NewClusterRole())
|
||||
}
|
||||
|
||||
// NewClusterRoleListWithArgs returns a new resource list.
|
||||
func NewClusterRoleListWithArgs(ns string, res Resource) List {
|
||||
return newList(NotNamespaced, "clusterrole", res, CRUDAccess)
|
||||
}
|
||||
|
||||
// NewClusterRole instantiates a new ClusterRole.
|
||||
func NewClusterRole() *ClusterRole {
|
||||
return NewClusterRoleWithArgs(k8s.NewClusterRole())
|
||||
}
|
||||
|
||||
// NewClusterRoleWithArgs instantiates a new Context.
|
||||
func NewClusterRoleWithArgs(r k8s.Res) *ClusterRole {
|
||||
ctx := &ClusterRole{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ctx.creator = ctx
|
||||
return ctx
|
||||
}
|
||||
|
||||
// NewInstance builds a new Context instance from a k8s resource.
|
||||
func (r *ClusterRole) NewInstance(i interface{}) Columnar {
|
||||
c := NewClusterRole()
|
||||
switch i.(type) {
|
||||
case *v1.ClusterRole:
|
||||
c.instance = i.(*v1.ClusterRole)
|
||||
case v1.ClusterRole:
|
||||
ii := i.(v1.ClusterRole)
|
||||
c.instance = &ii
|
||||
default:
|
||||
log.Fatalf("unknown context type %#v", i)
|
||||
}
|
||||
c.path = c.instance.Name
|
||||
return c
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *ClusterRole) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cr := i.(*v1.ClusterRole)
|
||||
cr.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1"
|
||||
cr.TypeMeta.Kind = "ClusterRole"
|
||||
raw, err := yaml.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*ClusterRole) Header(ns string) Row {
|
||||
return append(Row{}, "NAME", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *ClusterRole) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
|
||||
return append(ff,
|
||||
Pad(i.Name, 70),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*ClusterRole) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/api/rbac/v1"
|
||||
)
|
||||
|
||||
// ClusterRoleBinding tracks a kubernetes resource.
|
||||
type ClusterRoleBinding struct {
|
||||
*Base
|
||||
instance *v1.ClusterRoleBinding
|
||||
}
|
||||
|
||||
// NewClusterRoleBindingList returns a new resource list.
|
||||
func NewClusterRoleBindingList(ns string) List {
|
||||
return NewClusterRoleBindingListWithArgs(ns, NewClusterRoleBinding())
|
||||
}
|
||||
|
||||
// NewClusterRoleBindingListWithArgs returns a new resource list.
|
||||
func NewClusterRoleBindingListWithArgs(ns string, res Resource) List {
|
||||
return newList(NotNamespaced, "ctx", res, SwitchAccess|DeleteAccess)
|
||||
}
|
||||
|
||||
// NewClusterRoleBinding instantiates a new ClusterRoleBinding.
|
||||
func NewClusterRoleBinding() *ClusterRoleBinding {
|
||||
return NewClusterRoleBindingWithArgs(k8s.NewClusterRoleBinding())
|
||||
}
|
||||
|
||||
// NewClusterRoleBindingWithArgs instantiates a new Context.
|
||||
func NewClusterRoleBindingWithArgs(r k8s.Res) *ClusterRoleBinding {
|
||||
ctx := &ClusterRoleBinding{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ctx.creator = ctx
|
||||
return ctx
|
||||
}
|
||||
|
||||
// NewInstance builds a new Context instance from a k8s resource.
|
||||
func (r *ClusterRoleBinding) NewInstance(i interface{}) Columnar {
|
||||
c := NewClusterRoleBinding()
|
||||
switch i.(type) {
|
||||
case *v1.ClusterRoleBinding:
|
||||
c.instance = i.(*v1.ClusterRoleBinding)
|
||||
case v1.ClusterRoleBinding:
|
||||
ii := i.(v1.ClusterRoleBinding)
|
||||
c.instance = &ii
|
||||
default:
|
||||
log.Fatalf("unknown context type %#v", i)
|
||||
}
|
||||
c.path = c.instance.Name
|
||||
return c
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *ClusterRoleBinding) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
crb := i.(*v1.ClusterRoleBinding)
|
||||
crb.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1"
|
||||
crb.TypeMeta.Kind = "ClusterRoleBinding"
|
||||
raw, err := yaml.Marshal(crb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*ClusterRoleBinding) Header(_ string) Row {
|
||||
return append(Row{}, "NAME", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *ClusterRoleBinding) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*ClusterRoleBinding) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestCRBHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "AGE"}, newCRB().Header("default"))
|
||||
}
|
||||
|
||||
func TestCRBHeaderAllNS(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "AGE"}, newCRB().Header(resource.AllNamespaces))
|
||||
}
|
||||
|
||||
func TestCRBFields(t *testing.T) {
|
||||
r := newCRB().Fields(resource.AllNamespaces)
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestCRBMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sCRB(), nil)
|
||||
|
||||
cm := resource.NewClusterRoleBindingWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, crbYaml(), ma)
|
||||
}
|
||||
|
||||
func TestCRBListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sCRB()}, nil)
|
||||
|
||||
l := resource.NewClusterRoleBindingListWithArgs("-", resource.NewClusterRoleBindingWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["fred"]
|
||||
assert.Equal(t, 2, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sCRB() *rbacv1.ClusterRoleBinding {
|
||||
return &rbacv1.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "fred",
|
||||
Namespace: "blee",
|
||||
CreationTimestamp: metav1.Time{testTime()},
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
rbacv1.Subject{Kind: "test", Name: "fred", Namespace: "blee"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newCRB() resource.Columnar {
|
||||
return resource.NewClusterRoleBinding().NewInstance(k8sCRB())
|
||||
}
|
||||
|
||||
func crbYaml() string {
|
||||
return `typemeta:
|
||||
kind: ClusterRoleBinding
|
||||
apiversion: rbac.authorization.k8s.io/v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
subjects:
|
||||
- kind: test
|
||||
apigroup: ""
|
||||
name: fred
|
||||
namespace: blee
|
||||
roleref:
|
||||
apigroup: ""
|
||||
kind: ""
|
||||
name: ""
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestCRListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewClusterRoleList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.Equal(t, "clusterrole", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCRHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "AGE"}, newClusterRole().Header("default"))
|
||||
}
|
||||
|
||||
func TestCRHeaderAllNS(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "AGE"}, newClusterRole().Header(resource.AllNamespaces))
|
||||
}
|
||||
|
||||
func TestCRFields(t *testing.T) {
|
||||
r := newClusterRole().Fields("blee")
|
||||
assert.Equal(t, "fred"+strings.Repeat(" ", 66), r[0])
|
||||
}
|
||||
|
||||
func TestCRFieldsAllNS(t *testing.T) {
|
||||
r := newClusterRole().Fields(resource.AllNamespaces)
|
||||
assert.Equal(t, "fred"+strings.Repeat(" ", 66), r[0])
|
||||
}
|
||||
|
||||
func TestCRMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sCR(), nil)
|
||||
|
||||
cm := resource.NewClusterRoleWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, crYaml(), ma)
|
||||
}
|
||||
|
||||
func TestCRListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sCR()}, nil)
|
||||
|
||||
l := resource.NewClusterRoleListWithArgs("-", resource.NewClusterRoleWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["fred"]
|
||||
assert.Equal(t, 2, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred" + strings.Repeat(" ", 66)}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestCRListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sCR(), nil)
|
||||
l := resource.NewClusterRoleListWithArgs("blee", resource.NewClusterRoleWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sCR() *rbacv1.ClusterRole {
|
||||
return &rbacv1.ClusterRole{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "fred",
|
||||
Namespace: "blee",
|
||||
CreationTimestamp: metav1.Time{testTime()},
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
rbacv1.PolicyRule{
|
||||
Verbs: []string{"get", "list"},
|
||||
APIGroups: []string{""},
|
||||
ResourceNames: []string{"pod"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newClusterRole() resource.Columnar {
|
||||
return resource.NewClusterRole().NewInstance(k8sCR())
|
||||
}
|
||||
|
||||
func testTime() time.Time {
|
||||
t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00")
|
||||
if err != nil {
|
||||
log.Println("TestTime Failed", err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func crYaml() string {
|
||||
return `typemeta:
|
||||
kind: ClusterRole
|
||||
apiversion: rbac.authorization.k8s.io/v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
rules:
|
||||
- verbs:
|
||||
- get
|
||||
- list
|
||||
apigroups:
|
||||
- ""
|
||||
resources: []
|
||||
resourcenames:
|
||||
- pod
|
||||
nonresourceurls: []
|
||||
aggregationrule: null
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
// CRD tracks a kubernetes resource.
|
||||
type CRD struct {
|
||||
*Base
|
||||
instance *unstructured.Unstructured
|
||||
}
|
||||
|
||||
// NewCRDList returns a new resource list.
|
||||
func NewCRDList(ns string) List {
|
||||
return NewCRDListWithArgs(ns, NewCRD())
|
||||
}
|
||||
|
||||
// NewCRDListWithArgs returns a new resource list.
|
||||
func NewCRDListWithArgs(ns string, res Resource) List {
|
||||
return newList(NotNamespaced, "crd", res, CRUDAccess)
|
||||
}
|
||||
|
||||
// NewCRD instantiates a new CRD.
|
||||
func NewCRD() *CRD {
|
||||
return NewCRDWithArgs(k8s.NewCRD())
|
||||
}
|
||||
|
||||
// NewCRDWithArgs instantiates a new Context.
|
||||
func NewCRDWithArgs(r k8s.Res) *CRD {
|
||||
ctx := &CRD{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ctx.creator = ctx
|
||||
return ctx
|
||||
}
|
||||
|
||||
// NewInstance builds a new Context instance from a k8s resource.
|
||||
func (r *CRD) NewInstance(i interface{}) Columnar {
|
||||
c := NewCRD()
|
||||
switch i.(type) {
|
||||
case *unstructured.Unstructured:
|
||||
c.instance = i.(*unstructured.Unstructured)
|
||||
case unstructured.Unstructured:
|
||||
ii := i.(unstructured.Unstructured)
|
||||
c.instance = &ii
|
||||
default:
|
||||
log.Fatalf("unknown context type %#v", i)
|
||||
}
|
||||
meta := c.instance.Object["metadata"].(map[string]interface{})
|
||||
c.path = meta["name"].(string)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Marshal a resource.
|
||||
func (r *CRD) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
raw, err := yaml.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return the resource header.
|
||||
func (*CRD) Header(ns string) Row {
|
||||
return Row{"NAME", "AGE"}
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *CRD) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
|
||||
i := r.instance
|
||||
meta := i.Object["metadata"].(map[string]interface{})
|
||||
t, err := time.Parse(time.RFC3339, meta["creationTimestamp"].(string))
|
||||
if err != nil {
|
||||
log.Error("Fields timestamp", err)
|
||||
}
|
||||
return append(ff, meta["name"].(string), toAge(metav1.Time{t}))
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields.
|
||||
func (r *CRD) ExtFields() Properties {
|
||||
var (
|
||||
pp = Properties{}
|
||||
i = r.instance
|
||||
)
|
||||
|
||||
meta := i.Object["metadata"].(map[string]interface{})
|
||||
|
||||
if spec, ok := i.Object["spec"].(map[string]interface{}); ok {
|
||||
pp["name"] = meta["name"]
|
||||
pp["group"], pp["version"] = spec["group"], spec["version"]
|
||||
names := spec["names"].(map[string]interface{})
|
||||
pp["kind"] = names["kind"]
|
||||
pp["singular"], pp["plural"] = names["singular"], names["plural"]
|
||||
pp["aliases"] = names["shortNames"]
|
||||
}
|
||||
return pp
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func TestCRDListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewCRDList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.Equal(t, "crd", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCRDHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "AGE"}, newCRD().Header("default"))
|
||||
}
|
||||
|
||||
func TestCRDHeaderAllNS(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "AGE"}, newCRD().Header(resource.AllNamespaces))
|
||||
}
|
||||
|
||||
func TestCRDFields(t *testing.T) {
|
||||
r := newCRD().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestCRDFieldsAllNS(t *testing.T) {
|
||||
r := newCRD().Fields(resource.AllNamespaces)
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestCRDMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sCRD(), nil)
|
||||
|
||||
cm := resource.NewCRDWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, crdYaml(), ma)
|
||||
}
|
||||
|
||||
func TestCRDListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sCRD()}, nil)
|
||||
|
||||
l := resource.NewCRDListWithArgs("-", resource.NewCRDWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["fred"]
|
||||
assert.Equal(t, 2, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestCRDListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sCRD(), nil)
|
||||
l := resource.NewCRDListWithArgs("blee", resource.NewCRDWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sCRD() *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"namespace": "blee",
|
||||
"name": "fred",
|
||||
"creationTimestamp": "2018-12-14T10:36:43.326972Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newCRD() resource.Columnar {
|
||||
return resource.NewCRD().NewInstance(k8sCRD())
|
||||
}
|
||||
|
||||
func crdYaml() string {
|
||||
return `object:
|
||||
metadata:
|
||||
creationTimestamp: "2018-12-14T10:36:43.326972Z"
|
||||
name: fred
|
||||
namespace: blee
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
)
|
||||
|
||||
// CronJob tracks a kubernetes resource.
|
||||
type CronJob struct {
|
||||
*Base
|
||||
instance *batchv1beta1.CronJob
|
||||
}
|
||||
|
||||
// NewCronJobList returns a new resource list.
|
||||
func NewCronJobList(ns string) List {
|
||||
return NewCronJobListWithArgs(ns, NewCronJob())
|
||||
}
|
||||
|
||||
// NewCronJobListWithArgs returns a new resource list.
|
||||
func NewCronJobListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "job", res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewCronJob instantiates a new CronJob.
|
||||
func NewCronJob() *CronJob {
|
||||
return NewCronJobWithArgs(k8s.NewCronJob())
|
||||
}
|
||||
|
||||
// NewCronJobWithArgs instantiates a new CronJob.
|
||||
func NewCronJobWithArgs(r k8s.Res) *CronJob {
|
||||
cm := &CronJob{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
cm.creator = cm
|
||||
return cm
|
||||
}
|
||||
|
||||
// NewInstance builds a new CronJob instance from a k8s resource.
|
||||
func (*CronJob) NewInstance(i interface{}) Columnar {
|
||||
job := NewCronJob()
|
||||
switch i.(type) {
|
||||
case *batchv1beta1.CronJob:
|
||||
job.instance = i.(*batchv1beta1.CronJob)
|
||||
case batchv1beta1.CronJob:
|
||||
ii := i.(batchv1beta1.CronJob)
|
||||
job.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
job.path = job.namespacedName(job.instance.ObjectMeta)
|
||||
return job
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *CronJob) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dp := i.(*batchv1beta1.CronJob)
|
||||
dp.TypeMeta.APIVersion = "extensions/batchv1beta1"
|
||||
dp.TypeMeta.Kind = "CronJob"
|
||||
raw, err := yaml.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*CronJob) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh, "NAME", "SCHEDULE", "SUSPEND", "ACTIVE", "LAST_SCHEDULE", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *CronJob) Fields(ns string) Row {
|
||||
ff := make([]string, 0, len(r.Header(ns)))
|
||||
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
lastScheduled := "<none>"
|
||||
if i.Status.LastScheduleTime != nil {
|
||||
lastScheduled = toAge(*i.Status.LastScheduleTime)
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
i.Spec.Schedule,
|
||||
boolToStr(*i.Spec.Suspend),
|
||||
strconv.Itoa(len(i.Status.Active)),
|
||||
lastScheduled,
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*CronJob) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestCronJobListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewCronJobList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.Equal(t, "job", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCronJobHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "SCHEDULE", "SUSPEND", "ACTIVE", "LAST_SCHEDULE", "AGE"}, newCronJob().Header("default"))
|
||||
}
|
||||
|
||||
func TestCronJobFields(t *testing.T) {
|
||||
r := newCronJob().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestCronJobMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sCronJob(), nil)
|
||||
|
||||
cm := resource.NewCronJobWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, cronjobYaml(), ma)
|
||||
}
|
||||
|
||||
func TestCronJobListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sCronJob()}, nil)
|
||||
|
||||
l := resource.NewCronJobListWithArgs("-", resource.NewCronJobWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 6, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestCronJobListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sCronJob(), nil)
|
||||
l := resource.NewCronJobListWithArgs("blee", resource.NewCronJobWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sCronJob() *batchv1beta1.CronJob {
|
||||
var b bool
|
||||
return &batchv1beta1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Spec: batchv1beta1.CronJobSpec{
|
||||
Schedule: "*/1 * * * *",
|
||||
Suspend: &b,
|
||||
},
|
||||
Status: batchv1beta1.CronJobStatus{
|
||||
LastScheduleTime: &metav1.Time{Time: testTime()},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newCronJob() resource.Columnar {
|
||||
return resource.NewCronJob().NewInstance(k8sCronJob())
|
||||
}
|
||||
|
||||
func cronjobYaml() string {
|
||||
return `typemeta:
|
||||
kind: CronJob
|
||||
apiversion: extensions/batchv1beta1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
schedule: '*/1 * * * *'
|
||||
startingdeadlineseconds: null
|
||||
concurrencypolicy: ""
|
||||
suspend: false
|
||||
jobtemplate:
|
||||
objectmeta:
|
||||
name: ""
|
||||
generatename: ""
|
||||
namespace: ""
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "0001-01-01T00:00:00Z"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
parallelism: null
|
||||
completions: null
|
||||
activedeadlineseconds: null
|
||||
backofflimit: null
|
||||
selector: null
|
||||
manualselector: null
|
||||
template:
|
||||
objectmeta:
|
||||
name: ""
|
||||
generatename: ""
|
||||
namespace: ""
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "0001-01-01T00:00:00Z"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
volumes: []
|
||||
initcontainers: []
|
||||
containers: []
|
||||
restartpolicy: ""
|
||||
terminationgraceperiodseconds: null
|
||||
activedeadlineseconds: null
|
||||
dnspolicy: ""
|
||||
nodeselector: {}
|
||||
serviceaccountname: ""
|
||||
deprecatedserviceaccount: ""
|
||||
automountserviceaccounttoken: null
|
||||
nodename: ""
|
||||
hostnetwork: false
|
||||
hostpid: false
|
||||
hostipc: false
|
||||
shareprocessnamespace: null
|
||||
securitycontext: null
|
||||
imagepullsecrets: []
|
||||
hostname: ""
|
||||
subdomain: ""
|
||||
affinity: null
|
||||
schedulername: ""
|
||||
tolerations: []
|
||||
hostaliases: []
|
||||
priorityclassname: ""
|
||||
priority: null
|
||||
dnsconfig: null
|
||||
readinessgates: []
|
||||
runtimeclassname: null
|
||||
enableservicelinks: null
|
||||
ttlsecondsafterfinished: null
|
||||
successfuljobshistorylimit: null
|
||||
failedjobshistorylimit: null
|
||||
status:
|
||||
active: []
|
||||
lastscheduletime: "2018-12-14T10:36:43.326972-07:00"
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
)
|
||||
|
||||
// Custom tracks a kubernetes resource.
|
||||
type Custom struct {
|
||||
*Base
|
||||
// instance *unstructured.Unstructured
|
||||
instance *metav1beta1.TableRow
|
||||
group, version, name string
|
||||
headers Row
|
||||
}
|
||||
|
||||
// NewCustomList returns a new resource list.
|
||||
func NewCustomList(ns, g, v, n string) List {
|
||||
return NewCustomListWithArgs(ns, n, NewCustom(g, v, n))
|
||||
}
|
||||
|
||||
// NewCustomListWithArgs returns a new resource list.
|
||||
func NewCustomListWithArgs(ns, n string, res Resource) List {
|
||||
return newList(ns, n, res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewCustom instantiates a new Kubernetes Resource.
|
||||
func NewCustom(g, v, n string) *Custom {
|
||||
return NewCustomWithArgs(k8s.NewResource(g, v, n))
|
||||
}
|
||||
|
||||
// NewCustomWithArgs instantiates a new Custom.
|
||||
func NewCustomWithArgs(r k8s.Res) *Custom {
|
||||
cr := &Custom{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
cr.creator = cr
|
||||
|
||||
cr.group, cr.version, cr.name = r.(*k8s.Resource).GetInfo()
|
||||
return cr
|
||||
}
|
||||
|
||||
// NewInstance builds a new Custom instance from a k8s resource.
|
||||
func (*Custom) NewInstance(i interface{}) Columnar {
|
||||
cr := NewCustom("", "", "")
|
||||
switch i.(type) {
|
||||
case *metav1beta1.TableRow:
|
||||
cr.instance = i.(*metav1beta1.TableRow)
|
||||
case metav1beta1.TableRow:
|
||||
t := i.(metav1beta1.TableRow)
|
||||
cr.instance = &t
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
var obj map[string]interface{}
|
||||
err := json.Unmarshal(cr.instance.Object.Raw, &obj)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
meta := obj["metadata"].(map[string]interface{})
|
||||
cr.path = path.Join(meta["namespace"].(string), meta["name"].(string))
|
||||
cr.group, cr.version, cr.name = obj["kind"].(string), obj["apiVersion"].(string), meta["name"].(string)
|
||||
return cr
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *Custom) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
raw, err := yaml.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// List all resources
|
||||
func (r *Custom) List(ns string) (Columnars, error) {
|
||||
ii, err := r.caller.List(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ii) != 1 {
|
||||
return Columnars{}, errors.New("no resources found")
|
||||
}
|
||||
|
||||
table := ii[0].(*metav1beta1.Table)
|
||||
r.headers = make(Row, len(table.ColumnDefinitions))
|
||||
for i, h := range table.ColumnDefinitions {
|
||||
r.headers[i] = h.Name
|
||||
}
|
||||
rows := table.Rows
|
||||
cc := make(Columnars, 0, len(rows))
|
||||
for i := 0; i < len(rows); i++ {
|
||||
cc = append(cc, r.creator.NewInstance(rows[i]))
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (r *Custom) Header(ns string) Row {
|
||||
hh := make(Row, 0, len(r.headers)+1)
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
|
||||
for _, h := range r.headers {
|
||||
hh = append(hh, strings.ToUpper(h))
|
||||
}
|
||||
return hh
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *Custom) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
|
||||
var obj map[string]interface{}
|
||||
err := json.Unmarshal(r.instance.Object.Raw, &obj)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return Row{}
|
||||
}
|
||||
|
||||
meta := obj["metadata"].(map[string]interface{})
|
||||
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, meta["namespace"].(string))
|
||||
}
|
||||
for _, c := range r.instance.Cells {
|
||||
ff = append(ff, fmt.Sprintf("%v", c))
|
||||
}
|
||||
|
||||
return ff
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*Custom) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
||||
func getCRDS() map[string]k8s.ApiGroup {
|
||||
m := map[string]k8s.ApiGroup{}
|
||||
list := NewCRDList("")
|
||||
ll, _ := list.Resource().List("")
|
||||
for _, l := range ll {
|
||||
ff := l.ExtFields()
|
||||
grp := k8s.ApiGroup{
|
||||
Resource: ff["name"].(string),
|
||||
Version: ff["version"].(string),
|
||||
Group: ff["group"].(string),
|
||||
Kind: ff["kind"].(string),
|
||||
}
|
||||
if aa, ok := ff["aliases"].([]interface{}); ok {
|
||||
if n, ok := ff["plural"].(string); ok {
|
||||
grp.Plural = n
|
||||
}
|
||||
if n, ok := ff["singular"].(string); ok {
|
||||
grp.Singular = n
|
||||
}
|
||||
aliases := make([]string, len(aa))
|
||||
for i, a := range aa {
|
||||
aliases[i] = a.(string)
|
||||
}
|
||||
grp.Aliases = aliases
|
||||
} else if s, ok := ff["singular"].(string); ok {
|
||||
grp.Singular = s
|
||||
if p, ok := ff["plural"].(string); ok {
|
||||
grp.Plural = p
|
||||
}
|
||||
} else if s, ok := ff["plural"].(string); ok {
|
||||
grp.Plural = s
|
||||
}
|
||||
m[grp.Kind] = grp
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"k8s.io/api/apps/v1"
|
||||
)
|
||||
|
||||
// Deployment tracks a kubernetes resource.
|
||||
type Deployment struct {
|
||||
*Base
|
||||
instance *v1.Deployment
|
||||
}
|
||||
|
||||
// NewDeploymentList returns a new resource list.
|
||||
func NewDeploymentList(ns string) List {
|
||||
return NewDeploymentListWithArgs(ns, NewDeployment())
|
||||
}
|
||||
|
||||
// NewDeploymentListWithArgs returns a new resource list.
|
||||
func NewDeploymentListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "deploy", res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewDeployment instantiates a new Deployment.
|
||||
func NewDeployment() *Deployment {
|
||||
return NewDeploymentWithArgs(k8s.NewDeployment())
|
||||
}
|
||||
|
||||
// NewDeploymentWithArgs instantiates a new Deployment.
|
||||
func NewDeploymentWithArgs(r k8s.Res) *Deployment {
|
||||
cm := &Deployment{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
cm.creator = cm
|
||||
return cm
|
||||
}
|
||||
|
||||
// NewInstance builds a new Deployment instance from a k8s resource.
|
||||
func (*Deployment) NewInstance(i interface{}) Columnar {
|
||||
cm := NewDeployment()
|
||||
switch i.(type) {
|
||||
case *v1.Deployment:
|
||||
cm.instance = i.(*v1.Deployment)
|
||||
case v1.Deployment:
|
||||
ii := i.(v1.Deployment)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *Deployment) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dp := i.(*v1.Deployment)
|
||||
dp.TypeMeta.APIVersion = "apps/v1"
|
||||
dp.TypeMeta.Kind = "Deployment"
|
||||
raw, err := yaml.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*Deployment) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh, "NAME", "DESIRED", "CURRENT", "UP-TO-DATE", "AVAILABLE", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *Deployment) Fields(ns string) Row {
|
||||
ff := make([]string, 0, len(r.Header(ns)))
|
||||
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
return append(ff,
|
||||
i.Name,
|
||||
strconv.Itoa(int(*i.Spec.Replicas)),
|
||||
strconv.Itoa(int(i.Status.Replicas)),
|
||||
strconv.Itoa(int(i.Status.UpdatedReplicas)),
|
||||
strconv.Itoa(int(i.Status.AvailableReplicas)),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*Deployment) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestDeploymentListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewDeploymentList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.Equal(t, "deploy", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "DESIRED", "CURRENT", "UP-TO-DATE", "AVAILABLE", "AGE"}, newDeployment().Header("default"))
|
||||
}
|
||||
|
||||
func TestDeploymentFields(t *testing.T) {
|
||||
r := newDeployment().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestDeploymentMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sDeployment(), nil)
|
||||
|
||||
cm := resource.NewDeploymentWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, dpYaml(), ma)
|
||||
}
|
||||
|
||||
func TestDeploymentListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sDeployment()}, nil)
|
||||
|
||||
l := resource.NewDeploymentListWithArgs("-", resource.NewDeploymentWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 6, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestDeploymentListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sDeployment(), nil)
|
||||
l := resource.NewDeploymentListWithArgs("blee", resource.NewDeploymentWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sDeployment() *appsv1.Deployment {
|
||||
var i int32 = 1
|
||||
return &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: &i,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newDeployment() resource.Columnar {
|
||||
return resource.NewDeployment().NewInstance(k8sDeployment())
|
||||
}
|
||||
|
||||
func dpYaml() string {
|
||||
return `typemeta:
|
||||
kind: Deployment
|
||||
apiversion: apps/v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
replicas: 1
|
||||
selector: null
|
||||
template:
|
||||
objectmeta:
|
||||
name: ""
|
||||
generatename: ""
|
||||
namespace: ""
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "0001-01-01T00:00:00Z"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
volumes: []
|
||||
initcontainers: []
|
||||
containers: []
|
||||
restartpolicy: ""
|
||||
terminationgraceperiodseconds: null
|
||||
activedeadlineseconds: null
|
||||
dnspolicy: ""
|
||||
nodeselector: {}
|
||||
serviceaccountname: ""
|
||||
deprecatedserviceaccount: ""
|
||||
automountserviceaccounttoken: null
|
||||
nodename: ""
|
||||
hostnetwork: false
|
||||
hostpid: false
|
||||
hostipc: false
|
||||
shareprocessnamespace: null
|
||||
securitycontext: null
|
||||
imagepullsecrets: []
|
||||
hostname: ""
|
||||
subdomain: ""
|
||||
affinity: null
|
||||
schedulername: ""
|
||||
tolerations: []
|
||||
hostaliases: []
|
||||
priorityclassname: ""
|
||||
priority: null
|
||||
dnsconfig: null
|
||||
readinessgates: []
|
||||
runtimeclassname: null
|
||||
enableservicelinks: null
|
||||
strategy:
|
||||
type: ""
|
||||
rollingupdate: null
|
||||
minreadyseconds: 0
|
||||
revisionhistorylimit: null
|
||||
paused: false
|
||||
progressdeadlineseconds: null
|
||||
status:
|
||||
observedgeneration: 0
|
||||
replicas: 0
|
||||
updatedreplicas: 0
|
||||
readyreplicas: 0
|
||||
availablereplicas: 0
|
||||
unavailablereplicas: 0
|
||||
conditions: []
|
||||
collisioncount: null
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
extv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
)
|
||||
|
||||
// DaemonSet tracks a kubernetes resource.
|
||||
type DaemonSet struct {
|
||||
*Base
|
||||
instance *extv1beta1.DaemonSet
|
||||
}
|
||||
|
||||
// NewDaemonSetList returns a new resource list.
|
||||
func NewDaemonSetList(ns string) List {
|
||||
return NewDaemonSetListWithArgs(ns, NewDaemonSet())
|
||||
}
|
||||
|
||||
// NewDaemonSetListWithArgs returns a new resource list.
|
||||
func NewDaemonSetListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "ds", res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewDaemonSet instantiates a new DaemonSet.
|
||||
func NewDaemonSet() *DaemonSet {
|
||||
return NewDaemonSetWithArgs(k8s.NewDaemonSet())
|
||||
}
|
||||
|
||||
// NewDaemonSetWithArgs instantiates a new DaemonSet.
|
||||
func NewDaemonSetWithArgs(r k8s.Res) *DaemonSet {
|
||||
cm := &DaemonSet{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
cm.creator = cm
|
||||
return cm
|
||||
}
|
||||
|
||||
// NewInstance builds a new DaemonSet instance from a k8s resource.
|
||||
func (*DaemonSet) NewInstance(i interface{}) Columnar {
|
||||
cm := NewDaemonSet()
|
||||
switch i.(type) {
|
||||
case *extv1beta1.DaemonSet:
|
||||
cm.instance = i.(*extv1beta1.DaemonSet)
|
||||
case extv1beta1.DaemonSet:
|
||||
ii := i.(extv1beta1.DaemonSet)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *DaemonSet) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dp := i.(*extv1beta1.DaemonSet)
|
||||
dp.TypeMeta.APIVersion = "extensions/v1beta1"
|
||||
dp.TypeMeta.Kind = "DaemonSet"
|
||||
raw, err := yaml.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*DaemonSet) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
hh = append(hh, "NAME", "DESIRED", "CURRENT", "READY", "UP-TO-DATE")
|
||||
hh = append(hh, "AVAILABLE", "NODE_SELECTOR", "AGE")
|
||||
return hh
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *DaemonSet) Fields(ns string) Row {
|
||||
ff := make([]string, 0, len(r.Header(ns)))
|
||||
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
return append(ff,
|
||||
i.Name,
|
||||
strconv.Itoa(int(i.Status.DesiredNumberScheduled)),
|
||||
strconv.Itoa(int(i.Status.CurrentNumberScheduled)),
|
||||
strconv.Itoa(int(i.Status.NumberReady)),
|
||||
strconv.Itoa(int(i.Status.UpdatedNumberScheduled)),
|
||||
strconv.Itoa(int(i.Status.NumberAvailable)),
|
||||
mapToStr(i.Spec.Template.Spec.NodeSelector),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*DaemonSet) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
extv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestDSListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewDaemonSetList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.Equal(t, "ds", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "DESIRED", "CURRENT", "READY", "UP-TO-DATE", "AVAILABLE", "NODE_SELECTOR", "AGE"}, newDS().Header("default"))
|
||||
}
|
||||
|
||||
func TestDSFields(t *testing.T) {
|
||||
r := newDS().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestDSMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sDS(), nil)
|
||||
|
||||
cm := resource.NewDaemonSetWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, dsYaml(), ma)
|
||||
}
|
||||
|
||||
func TestDSListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sDS()}, nil)
|
||||
|
||||
l := resource.NewDaemonSetListWithArgs("-", resource.NewDaemonSetWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 8, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestDSListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sDS(), nil)
|
||||
l := resource.NewDaemonSetListWithArgs("blee", resource.NewDaemonSetWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sDS() *extv1beta1.DaemonSet {
|
||||
return &extv1beta1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Spec: extv1beta1.DaemonSetSpec{
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"fred": "blee"},
|
||||
},
|
||||
},
|
||||
Status: extv1beta1.DaemonSetStatus{
|
||||
DesiredNumberScheduled: 1,
|
||||
CurrentNumberScheduled: 1,
|
||||
NumberReady: 1,
|
||||
NumberAvailable: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newDS() resource.Columnar {
|
||||
return resource.NewDaemonSet().NewInstance(k8sDS())
|
||||
}
|
||||
|
||||
func dsYaml() string {
|
||||
return `typemeta:
|
||||
kind: DaemonSet
|
||||
apiversion: extensions/v1beta1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
selector:
|
||||
matchlabels:
|
||||
fred: blee
|
||||
matchexpressions: []
|
||||
template:
|
||||
objectmeta:
|
||||
name: ""
|
||||
generatename: ""
|
||||
namespace: ""
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "0001-01-01T00:00:00Z"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
volumes: []
|
||||
initcontainers: []
|
||||
containers: []
|
||||
restartpolicy: ""
|
||||
terminationgraceperiodseconds: null
|
||||
activedeadlineseconds: null
|
||||
dnspolicy: ""
|
||||
nodeselector: {}
|
||||
serviceaccountname: ""
|
||||
deprecatedserviceaccount: ""
|
||||
automountserviceaccounttoken: null
|
||||
nodename: ""
|
||||
hostnetwork: false
|
||||
hostpid: false
|
||||
hostipc: false
|
||||
shareprocessnamespace: null
|
||||
securitycontext: null
|
||||
imagepullsecrets: []
|
||||
hostname: ""
|
||||
subdomain: ""
|
||||
affinity: null
|
||||
schedulername: ""
|
||||
tolerations: []
|
||||
hostaliases: []
|
||||
priorityclassname: ""
|
||||
priority: null
|
||||
dnsconfig: null
|
||||
readinessgates: []
|
||||
runtimeclassname: null
|
||||
enableservicelinks: null
|
||||
updatestrategy:
|
||||
type: ""
|
||||
rollingupdate: null
|
||||
minreadyseconds: 0
|
||||
templategeneration: 0
|
||||
revisionhistorylimit: null
|
||||
status:
|
||||
currentnumberscheduled: 1
|
||||
numbermisscheduled: 0
|
||||
desirednumberscheduled: 1
|
||||
numberready: 1
|
||||
observedgeneration: 0
|
||||
updatednumberscheduled: 0
|
||||
numberavailable: 1
|
||||
numberunavailable: 0
|
||||
collisioncount: null
|
||||
conditions: []
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Endpoints tracks a kubernetes resource.
|
||||
type Endpoints struct {
|
||||
*Base
|
||||
instance *v1.Endpoints
|
||||
}
|
||||
|
||||
// NewEndpointsList returns a new resource list.
|
||||
func NewEndpointsList(ns string) List {
|
||||
return NewEndpointsListWithArgs(ns, NewEndpoints())
|
||||
}
|
||||
|
||||
// NewEndpointsListWithArgs returns a new resource list.
|
||||
func NewEndpointsListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "ep", res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewEndpoints instantiates a new Endpoint.
|
||||
func NewEndpoints() *Endpoints {
|
||||
return NewEndpointsWithArgs(k8s.NewEndpoints())
|
||||
}
|
||||
|
||||
// NewEndpointsWithArgs instantiates a new Endpoint.
|
||||
func NewEndpointsWithArgs(r k8s.Res) *Endpoints {
|
||||
ep := &Endpoints{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ep.creator = ep
|
||||
return ep
|
||||
}
|
||||
|
||||
// NewInstance builds a new Endpoint instance from a k8s resource.
|
||||
func (*Endpoints) NewInstance(i interface{}) Columnar {
|
||||
cm := NewEndpoints()
|
||||
switch i.(type) {
|
||||
case *v1.Endpoints:
|
||||
cm.instance = i.(*v1.Endpoints)
|
||||
case v1.Endpoints:
|
||||
ii := i.(v1.Endpoints)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *Endpoints) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ep := i.(*v1.Endpoints)
|
||||
ep.TypeMeta.APIVersion = "v1"
|
||||
ep.TypeMeta.Kind = "Endpoints"
|
||||
raw, err := yaml.Marshal(ep)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*Endpoints) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh, "NAME", "ENDPOINTS", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *Endpoints) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
missing(r.toEPs(i.Subsets)),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*Endpoints) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
||||
func (r *Endpoints) toEPs(ss []v1.EndpointSubset) string {
|
||||
aa := make([]string, 0, len(ss))
|
||||
max := 3
|
||||
for _, s := range ss {
|
||||
pp := make([]string, 0, len(s.Ports))
|
||||
for _, p := range s.Ports {
|
||||
pp = append(pp, strconv.Itoa(int(p.Port)))
|
||||
}
|
||||
|
||||
for _, a := range s.Addresses {
|
||||
if len(a.IP) != 0 {
|
||||
if len(pp) == 0 {
|
||||
aa = append(aa, fmt.Sprintf("%s", a.IP))
|
||||
} else {
|
||||
add := fmt.Sprintf("%s:%s", a.IP, strings.Join(pp, ","))
|
||||
if len(pp) > max {
|
||||
add = fmt.Sprintf("%s:%s...", a.IP, strings.Join(pp[:max], ","))
|
||||
}
|
||||
aa = append(aa, add)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(aa, ",")
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestEndpointsListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewEndpointsList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.Equal(t, "ep", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndpointsHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "ENDPOINTS", "AGE"}, newEndpoints().Header("default"))
|
||||
}
|
||||
|
||||
func TestEndpointsFields(t *testing.T) {
|
||||
r := newEndpoints().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestEndpointsMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sEndpoints(), nil)
|
||||
|
||||
cm := resource.NewEndpointsWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, epYaml(), ma)
|
||||
}
|
||||
|
||||
func TestEndpointsListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sEndpoints()}, nil)
|
||||
|
||||
l := resource.NewEndpointsListWithArgs("-", resource.NewEndpointsWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 3, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestEndpointsListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sEndpoints(), nil)
|
||||
l := resource.NewEndpointsListWithArgs("blee", resource.NewEndpointsWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sEndpoints() *v1.Endpoints {
|
||||
return &v1.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Subsets: []v1.EndpointSubset{
|
||||
{
|
||||
Addresses: []v1.EndpointAddress{
|
||||
{IP: "1.1.1.1"},
|
||||
},
|
||||
Ports: []v1.EndpointPort{
|
||||
{Port: 80, Protocol: "TCP"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newEndpoints() resource.Columnar {
|
||||
return resource.NewEndpoints().NewInstance(k8sEndpoints())
|
||||
}
|
||||
|
||||
func epYaml() string {
|
||||
return `typemeta:
|
||||
kind: Endpoints
|
||||
apiversion: v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 1.1.1.1
|
||||
hostname: ""
|
||||
nodename: null
|
||||
targetref: null
|
||||
notreadyaddresses: []
|
||||
ports:
|
||||
- name: ""
|
||||
port: 80
|
||||
protocol: TCP
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Event tracks a kubernetes resource.
|
||||
type Event struct {
|
||||
*Base
|
||||
instance *v1.Event
|
||||
}
|
||||
|
||||
// NewEventList returns a new resource list.
|
||||
func NewEventList(ns string) List {
|
||||
return NewEventListWithArgs(ns, NewEvent())
|
||||
}
|
||||
|
||||
// NewEventListWithArgs returns a new resource list.
|
||||
func NewEventListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "event", res, ListAccess+NamespaceAccess)
|
||||
}
|
||||
|
||||
// NewEvent instantiates a new Endpoint.
|
||||
func NewEvent() *Event {
|
||||
return NewEventWithArgs(k8s.NewEvent())
|
||||
}
|
||||
|
||||
// NewEventWithArgs instantiates a new Endpoint.
|
||||
func NewEventWithArgs(r k8s.Res) *Event {
|
||||
ep := &Event{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ep.creator = ep
|
||||
return ep
|
||||
}
|
||||
|
||||
// NewInstance builds a new Endpoint instance from a k8s resource.
|
||||
func (*Event) NewInstance(i interface{}) Columnar {
|
||||
cm := NewEvent()
|
||||
switch i.(type) {
|
||||
case *v1.Event:
|
||||
cm.instance = i.(*v1.Event)
|
||||
case v1.Event:
|
||||
ii := i.(v1.Event)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *Event) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ev := i.(*v1.Event)
|
||||
ev.TypeMeta.APIVersion = "v1"
|
||||
ev.TypeMeta.Kind = "Event"
|
||||
raw, err := yaml.Marshal(ev)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// // Get resource given a namespaced name.
|
||||
// func (r *Event) Get(path string) (Columnar, error) {
|
||||
// ns, n := namespaced(path)
|
||||
// i, err := r.caller.Get(ns, n)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return r.NewInstance(i), nil
|
||||
// }
|
||||
|
||||
// Delete a resource by name.
|
||||
func (r *Event) Delete(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*Event) Header(ns string) Row {
|
||||
ff := Row{""}
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, "NAMESPACE")
|
||||
}
|
||||
return append(ff, "NAME", "REASON", "SOURCE", "COUNT", "MESSAGE", "AGE")
|
||||
}
|
||||
|
||||
// Fields returns display fields.
|
||||
func (r *Event) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
|
||||
ff = append(ff, r.toEmoji(i.Type, i.Reason))
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
rx := regexp.MustCompile(`(.+)\.(.+)`)
|
||||
return append(ff,
|
||||
// i.Name,
|
||||
rx.ReplaceAllString(i.Name, `$1`),
|
||||
i.Reason,
|
||||
i.Source.Component,
|
||||
strconv.Itoa(int(i.Count)),
|
||||
Truncate(i.Message, 50),
|
||||
toAge(i.LastTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*Event) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func (*Event) toEmoji(t, r string) string {
|
||||
switch t {
|
||||
case "Warning":
|
||||
switch r {
|
||||
case "Failed":
|
||||
return "😡"
|
||||
case "Killing":
|
||||
return "👿"
|
||||
default:
|
||||
return "😡"
|
||||
}
|
||||
default:
|
||||
switch r {
|
||||
case "Killing":
|
||||
return "👿"
|
||||
case "BackOff":
|
||||
return "👹"
|
||||
default:
|
||||
return "😮"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestEventListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewEventList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.Equal(t, "event", l.GetName())
|
||||
for _, a := range []int{resource.ListAccess, resource.NamespaceAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"", "NAME", "REASON", "SOURCE", "COUNT", "MESSAGE", "AGE"}, newEvent().Header("default"))
|
||||
}
|
||||
|
||||
func TestEventFields(t *testing.T) {
|
||||
r := newEvent().Fields("blee")
|
||||
assert.Equal(t, resource.Row{"😮", "fred", "blah", "", "1"}, r[:5])
|
||||
}
|
||||
|
||||
func TestEventMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sEvent(), nil)
|
||||
|
||||
cm := resource.NewEventWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, evYaml(), ma)
|
||||
}
|
||||
|
||||
func TestEventListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sEvent()}, nil)
|
||||
|
||||
l := resource.NewEventListWithArgs("-", resource.NewEventWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 7, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"😮"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestEventListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sEvent(), nil)
|
||||
l := resource.NewEventListWithArgs("blee", resource.NewEventWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sEvent() *v1.Event {
|
||||
return &v1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Reason: "blah",
|
||||
Message: "blee",
|
||||
Count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func newEvent() resource.Columnar {
|
||||
return resource.NewEvent().NewInstance(k8sEvent())
|
||||
}
|
||||
|
||||
func evYaml() string {
|
||||
return `typemeta:
|
||||
kind: Event
|
||||
apiversion: v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
involvedobject:
|
||||
kind: ""
|
||||
namespace: ""
|
||||
name: ""
|
||||
uid: ""
|
||||
apiversion: ""
|
||||
resourceversion: ""
|
||||
fieldpath: ""
|
||||
reason: blah
|
||||
message: blee
|
||||
source:
|
||||
component: ""
|
||||
host: ""
|
||||
firsttimestamp: "0001-01-01T00:00:00Z"
|
||||
lasttimestamp: "0001-01-01T00:00:00Z"
|
||||
count: 1
|
||||
type: ""
|
||||
eventtime: "0001-01-01T00:00:00Z"
|
||||
series: null
|
||||
action: ""
|
||||
related: null
|
||||
reportingcontroller: ""
|
||||
reportinginstance: ""
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/k8sland/tview"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
const (
|
||||
// AllNamespaces indicator to retrieve K8s resource for all namespaces
|
||||
AllNamespaces = ""
|
||||
// NotNamespaced indicator for non namespaced resource.
|
||||
NotNamespaced = "-"
|
||||
|
||||
// New track new resource events.
|
||||
New watch.EventType = "NEW"
|
||||
// Unchanged provides no change events.
|
||||
Unchanged watch.EventType = "UNCHANGED"
|
||||
|
||||
// MissingValue indicates an unset value.
|
||||
MissingValue = "<none>"
|
||||
// NAValue indicates a value that does not pertain.
|
||||
NAValue = "<n/a>"
|
||||
)
|
||||
|
||||
func namespaced(n string) (string, string) {
|
||||
ns, po := path.Split(n)
|
||||
return strings.Trim(ns, "/"), po
|
||||
}
|
||||
|
||||
func missing(s string) string {
|
||||
return check(s, MissingValue)
|
||||
}
|
||||
|
||||
func na(s string) string {
|
||||
return check(s, NAValue)
|
||||
}
|
||||
|
||||
func check(s, sub string) string {
|
||||
if len(s) == 0 {
|
||||
return sub
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func intToStr(i int64) string {
|
||||
return strconv.Itoa(int(i))
|
||||
}
|
||||
|
||||
func boolToStr(b bool) string {
|
||||
switch b {
|
||||
case true:
|
||||
return "true"
|
||||
default:
|
||||
return "false"
|
||||
}
|
||||
}
|
||||
|
||||
func toAge(timestamp metav1.Time) string {
|
||||
if timestamp.IsZero() {
|
||||
return "<unknown>"
|
||||
}
|
||||
return duration.HumanDuration(time.Since(timestamp.Time))
|
||||
}
|
||||
|
||||
// Pad a string up to the given length.
|
||||
func Pad(s string, l int) string {
|
||||
fmat := "%-" + strconv.Itoa(l) + "s"
|
||||
return fmt.Sprintf(fmat, s)
|
||||
}
|
||||
|
||||
// Truncate a string to the given l and suffix ellipsis if needed.
|
||||
func Truncate(s string, l int) string {
|
||||
if len(s) > l {
|
||||
fmat := "%." + strconv.Itoa(l) + "s%s"
|
||||
return fmt.Sprintf(fmat, s, string(tview.SemigraphicsHorizontalEllipsis))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func mapToStr(m map[string]string) (s string) {
|
||||
if len(m) == 0 {
|
||||
return MissingValue
|
||||
}
|
||||
|
||||
kk := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
kk = append(kk, k)
|
||||
}
|
||||
sort.Strings(kk)
|
||||
|
||||
for i, k := range kk {
|
||||
s += k + "=" + m[k]
|
||||
if i < len(kk)-1 {
|
||||
s += ","
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNamespaced(t *testing.T) {
|
||||
uu := []struct {
|
||||
p, ns, n string
|
||||
}{
|
||||
{"fred/blee", "fred", "blee"},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
ns, n := namespaced(u.p)
|
||||
assert.Equal(t, u.ns, ns)
|
||||
assert.Equal(t, u.n, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissing(t *testing.T) {
|
||||
uu := []struct {
|
||||
i, e string
|
||||
}{
|
||||
{"fred", "fred"},
|
||||
{"", MissingValue},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, missing(u.i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolToStr(t *testing.T) {
|
||||
uu := []struct {
|
||||
i bool
|
||||
e string
|
||||
}{
|
||||
{true, "true"},
|
||||
{false, "false"},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, boolToStr(u.i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNa(t *testing.T) {
|
||||
uu := []struct {
|
||||
i, e string
|
||||
}{
|
||||
{"fred", "fred"},
|
||||
{"", NAValue},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, na(u.i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPad(t *testing.T) {
|
||||
uu := []struct {
|
||||
s string
|
||||
l int
|
||||
e string
|
||||
}{
|
||||
{"fred", 10, "fred "},
|
||||
{"fred", 6, "fred "},
|
||||
{"fred", 4, "fred"},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, Pad(u.s, u.l))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncate(t *testing.T) {
|
||||
uu := []struct {
|
||||
s string
|
||||
l int
|
||||
e string
|
||||
}{
|
||||
{"fred", 2, "fr…"},
|
||||
{"fred", 1, "f…"},
|
||||
{"fred", 10, "fred"},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, Truncate(u.s, u.l))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapToStr(t *testing.T) {
|
||||
uu := []struct {
|
||||
i map[string]string
|
||||
e string
|
||||
}{
|
||||
{map[string]string{"blee": "duh", "aa": "bb"}, "aa=bb,blee=duh"},
|
||||
{map[string]string{}, MissingValue},
|
||||
}
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, mapToStr(u.i))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapToStr(b *testing.B) {
|
||||
ll := map[string]string{
|
||||
"blee": "duh",
|
||||
"aa": "bb",
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
mapToStr(ll)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"k8s.io/api/autoscaling/v1"
|
||||
)
|
||||
|
||||
// HPA tracks a kubernetes resource.
|
||||
type HPA struct {
|
||||
*Base
|
||||
instance *v1.HorizontalPodAutoscaler
|
||||
}
|
||||
|
||||
// NewHPAList returns a new resource list.
|
||||
func NewHPAList(ns string) List {
|
||||
return NewHPAListWithArgs(ns, NewHPA())
|
||||
}
|
||||
|
||||
// NewHPAListWithArgs returns a new resource list.
|
||||
func NewHPAListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "hpa", res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewHPA instantiates a new Endpoint.
|
||||
func NewHPA() *HPA {
|
||||
return NewHPAWithArgs(k8s.NewHPA())
|
||||
}
|
||||
|
||||
// NewHPAWithArgs instantiates a new Endpoint.
|
||||
func NewHPAWithArgs(r k8s.Res) *HPA {
|
||||
ep := &HPA{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ep.creator = ep
|
||||
return ep
|
||||
}
|
||||
|
||||
// NewInstance builds a new Endpoint instance from a k8s resource.
|
||||
func (*HPA) NewInstance(i interface{}) Columnar {
|
||||
cm := NewHPA()
|
||||
switch i.(type) {
|
||||
case *v1.HorizontalPodAutoscaler:
|
||||
cm.instance = i.(*v1.HorizontalPodAutoscaler)
|
||||
case v1.HorizontalPodAutoscaler:
|
||||
ii := i.(v1.HorizontalPodAutoscaler)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *HPA) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hpa := i.(*v1.HorizontalPodAutoscaler)
|
||||
hpa.TypeMeta.APIVersion = "autoscaling/v1"
|
||||
hpa.TypeMeta.Kind = "HPA"
|
||||
raw, err := yaml.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*HPA) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh,
|
||||
"NAME",
|
||||
"REFERENCE",
|
||||
"TARGETS",
|
||||
"MINPODS",
|
||||
"MAXPODS",
|
||||
"REPLICAS",
|
||||
"AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *HPA) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
target := "<unknown>"
|
||||
if i.Status.CurrentCPUUtilizationPercentage != nil {
|
||||
target = strconv.Itoa(int(*i.Status.CurrentCPUUtilizationPercentage))
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
i.ObjectMeta.Name,
|
||||
i.Spec.ScaleTargetRef.Name,
|
||||
fmt.Sprintf("%s٪/%d٪", target, *i.Spec.TargetCPUUtilizationPercentage),
|
||||
strconv.Itoa(int(*i.Spec.MinReplicas)),
|
||||
strconv.Itoa(int(i.Spec.MaxReplicas)),
|
||||
strconv.Itoa(int(i.Status.CurrentReplicas)),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*HPA) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/autoscaling/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestHPAListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewHPAList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.Equal(t, "hpa", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHPAHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "REFERENCE", "TARGETS", "MINPODS", "MAXPODS", "REPLICAS", "AGE"}, newHPA().Header("default"))
|
||||
}
|
||||
|
||||
func TestHPAFields(t *testing.T) {
|
||||
r := newHPA().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestHPAMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sHPA(), nil)
|
||||
|
||||
cm := resource.NewHPAWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, hpaYaml(), ma)
|
||||
}
|
||||
|
||||
func TestHPAListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sHPA()}, nil)
|
||||
|
||||
l := resource.NewHPAListWithArgs("-", resource.NewHPAWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 7, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestHPAListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sHPA(), nil)
|
||||
l := resource.NewHPAListWithArgs("blee", resource.NewHPAWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sHPA() *v1.HorizontalPodAutoscaler {
|
||||
var i int32 = 1
|
||||
return &v1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Spec: v1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: v1.CrossVersionObjectReference{
|
||||
Kind: "fred",
|
||||
Name: "blee",
|
||||
},
|
||||
MinReplicas: &i,
|
||||
MaxReplicas: 1,
|
||||
TargetCPUUtilizationPercentage: &i,
|
||||
},
|
||||
Status: v1.HorizontalPodAutoscalerStatus{
|
||||
CurrentReplicas: 1,
|
||||
CurrentCPUUtilizationPercentage: &i,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newHPA() resource.Columnar {
|
||||
return resource.NewHPA().NewInstance(k8sHPA())
|
||||
}
|
||||
|
||||
func hpaYaml() string {
|
||||
return `typemeta:
|
||||
kind: HPA
|
||||
apiversion: autoscaling/v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
scaletargetref:
|
||||
kind: fred
|
||||
name: blee
|
||||
apiversion: ""
|
||||
minreplicas: 1
|
||||
maxreplicas: 1
|
||||
targetcpuutilizationpercentage: 1
|
||||
status:
|
||||
observedgeneration: null
|
||||
lastscaletime: null
|
||||
currentreplicas: 1
|
||||
desiredreplicas: 0
|
||||
currentcpuutilizationpercentage: 1
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
)
|
||||
|
||||
// Ingress tracks a kubernetes resource.
|
||||
type Ingress struct {
|
||||
*Base
|
||||
instance *v1beta1.Ingress
|
||||
}
|
||||
|
||||
// NewIngressList returns a new resource list.
|
||||
func NewIngressList(ns string) List {
|
||||
return NewIngressListWithArgs(ns, NewIngress())
|
||||
}
|
||||
|
||||
// NewIngressListWithArgs returns a new resource list.
|
||||
func NewIngressListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "ing", res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewIngress instantiates a new Endpoint.
|
||||
func NewIngress() *Ingress {
|
||||
return NewIngressWithArgs(k8s.NewIngress())
|
||||
}
|
||||
|
||||
// NewIngressWithArgs instantiates a new Endpoint.
|
||||
func NewIngressWithArgs(r k8s.Res) *Ingress {
|
||||
ep := &Ingress{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ep.creator = ep
|
||||
return ep
|
||||
}
|
||||
|
||||
// NewInstance builds a new Endpoint instance from a k8s resource.
|
||||
func (*Ingress) NewInstance(i interface{}) Columnar {
|
||||
cm := NewIngress()
|
||||
switch i.(type) {
|
||||
case *v1beta1.Ingress:
|
||||
cm.instance = i.(*v1beta1.Ingress)
|
||||
case v1beta1.Ingress:
|
||||
ii := i.(v1beta1.Ingress)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *Ingress) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ing := i.(*v1beta1.Ingress)
|
||||
ing.TypeMeta.APIVersion = "extensions/v1beta1"
|
||||
ing.TypeMeta.Kind = "Ingress"
|
||||
raw, err := yaml.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*Ingress) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh, "NAME", "HOSTS", "ADDRESS", "PORT", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *Ingress) Fields(ns string) Row {
|
||||
ff := make([]string, 0, len(r.Header(ns)))
|
||||
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
r.toHosts(i.Spec.Rules),
|
||||
r.toAddress(i.Status.LoadBalancer),
|
||||
r.toPorts(i.Spec.TLS),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*Ingress) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func (*Ingress) toAddress(lbs v1.LoadBalancerStatus) string {
|
||||
ings := lbs.Ingress
|
||||
res := make([]string, 0, len(ings))
|
||||
for _, lb := range ings {
|
||||
if len(lb.IP) > 0 {
|
||||
res = append(res, lb.IP)
|
||||
} else if len(lb.Hostname) != 0 {
|
||||
res = append(res, lb.Hostname)
|
||||
}
|
||||
}
|
||||
return strings.Join(res, ",")
|
||||
}
|
||||
|
||||
func (*Ingress) toPorts(tls []v1beta1.IngressTLS) string {
|
||||
if len(tls) != 0 {
|
||||
return "80, 443"
|
||||
}
|
||||
return "80"
|
||||
}
|
||||
|
||||
func (*Ingress) toHosts(rr []v1beta1.IngressRule) string {
|
||||
var s string
|
||||
var i int
|
||||
for _, r := range rr {
|
||||
s += r.Host
|
||||
if i < len(rr)-1 {
|
||||
s += ","
|
||||
}
|
||||
i++
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestIngressListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewIngressList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.Equal(t, "ing", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "HOSTS", "ADDRESS", "PORT", "AGE"}, newIngress().Header("default"))
|
||||
}
|
||||
|
||||
func TestIngressFields(t *testing.T) {
|
||||
r := newIngress().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestIngressMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sIngress(), nil)
|
||||
|
||||
cm := resource.NewIngressWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ingYaml(), ma)
|
||||
}
|
||||
|
||||
func TestIngressListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sIngress()}, nil)
|
||||
|
||||
l := resource.NewIngressListWithArgs("-", resource.NewIngressWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 5, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestIngressListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sIngress(), nil)
|
||||
l := resource.NewIngressListWithArgs("blee", resource.NewIngressWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sIngress() *v1beta1.Ingress {
|
||||
return &v1beta1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Spec: v1beta1.IngressSpec{
|
||||
Rules: []v1beta1.IngressRule{
|
||||
{Host: "blee"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newIngress() resource.Columnar {
|
||||
return resource.NewIngress().NewInstance(k8sIngress())
|
||||
}
|
||||
|
||||
func ingYaml() string {
|
||||
return `typemeta:
|
||||
kind: Ingress
|
||||
apiversion: extensions/v1beta1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
backend: null
|
||||
tls: []
|
||||
rules:
|
||||
- host: blee
|
||||
ingressrulevalue:
|
||||
http: null
|
||||
status:
|
||||
loadbalancer:
|
||||
ingress: []
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"k8s.io/api/batch/v1"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
)
|
||||
|
||||
// Job tracks a kubernetes resource.
|
||||
type Job struct {
|
||||
*Base
|
||||
instance *v1.Job
|
||||
}
|
||||
|
||||
// NewJobList returns a new resource list.
|
||||
func NewJobList(ns string) List {
|
||||
return NewJobListWithArgs(ns, NewJob())
|
||||
}
|
||||
|
||||
// NewJobListWithArgs returns a new resource list.
|
||||
func NewJobListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "job", res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewJob instantiates a new Job.
|
||||
func NewJob() *Job {
|
||||
return NewJobWithArgs(k8s.NewJob())
|
||||
}
|
||||
|
||||
// NewJobWithArgs instantiates a new Job.
|
||||
func NewJobWithArgs(r k8s.Res) *Job {
|
||||
cm := &Job{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
cm.creator = cm
|
||||
return cm
|
||||
}
|
||||
|
||||
// NewInstance builds a new Job instance from a k8s resource.
|
||||
func (*Job) NewInstance(i interface{}) Columnar {
|
||||
job := NewJob()
|
||||
switch i.(type) {
|
||||
case *v1.Job:
|
||||
job.instance = i.(*v1.Job)
|
||||
case v1.Job:
|
||||
ii := i.(v1.Job)
|
||||
job.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
job.path = job.namespacedName(job.instance.ObjectMeta)
|
||||
return job
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *Job) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dp := i.(*v1.Job)
|
||||
dp.TypeMeta.APIVersion = "extensions/v1beta1"
|
||||
dp.TypeMeta.Kind = "Job"
|
||||
raw, err := yaml.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*Job) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh, "NAME", "COMPLETIONS", "DURATION", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *Job) Fields(ns string) Row {
|
||||
ff := make([]string, 0, len(r.Header(ns)))
|
||||
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
return append(ff,
|
||||
i.Name,
|
||||
r.toCompletion(i.Spec, i.Status),
|
||||
r.toDuration(i.Status),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*Job) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func (*Job) toCompletion(spec v1.JobSpec, status v1.JobStatus) (s string) {
|
||||
if spec.Completions != nil {
|
||||
return fmt.Sprintf("%d/%d", status.Succeeded, *spec.Completions)
|
||||
}
|
||||
var parallelism int32
|
||||
if spec.Parallelism != nil {
|
||||
parallelism = *spec.Parallelism
|
||||
}
|
||||
if parallelism > 1 {
|
||||
return fmt.Sprintf("%d/1 of %d", status.Succeeded, parallelism)
|
||||
}
|
||||
return fmt.Sprintf("%d/1", status.Succeeded)
|
||||
}
|
||||
|
||||
func (*Job) toDuration(status v1.JobStatus) string {
|
||||
switch {
|
||||
case status.StartTime == nil:
|
||||
case status.CompletionTime == nil:
|
||||
return duration.HumanDuration(time.Now().Sub(status.StartTime.Time))
|
||||
}
|
||||
return duration.HumanDuration(status.CompletionTime.Sub(status.StartTime.Time))
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/batch/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestJobListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewJobList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.Equal(t, "job", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "COMPLETIONS", "DURATION", "AGE"}, newJob().Header("default"))
|
||||
}
|
||||
|
||||
func TestJobFields(t *testing.T) {
|
||||
r := newJob().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestJobMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sJob(), nil)
|
||||
|
||||
cm := resource.NewJobWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, jobYaml(), ma)
|
||||
}
|
||||
|
||||
func TestJobListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sJob()}, nil)
|
||||
|
||||
l := resource.NewJobListWithArgs("-", resource.NewJobWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 4, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestJobListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sJob(), nil)
|
||||
l := resource.NewJobListWithArgs("blee", resource.NewJobWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sJob() *v1.Job {
|
||||
var i int32
|
||||
return &v1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Spec: v1.JobSpec{
|
||||
Completions: &i,
|
||||
Parallelism: &i,
|
||||
},
|
||||
Status: v1.JobStatus{
|
||||
StartTime: &metav1.Time{Time: testTime()},
|
||||
CompletionTime: &metav1.Time{Time: testTime()},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newJob() resource.Columnar {
|
||||
return resource.NewJob().NewInstance(k8sJob())
|
||||
}
|
||||
|
||||
func jobYaml() string {
|
||||
return `typemeta:
|
||||
kind: Job
|
||||
apiversion: extensions/v1beta1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
parallelism: 0
|
||||
completions: 0
|
||||
activedeadlineseconds: null
|
||||
backofflimit: null
|
||||
selector: null
|
||||
manualselector: null
|
||||
template:
|
||||
objectmeta:
|
||||
name: ""
|
||||
generatename: ""
|
||||
namespace: ""
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "0001-01-01T00:00:00Z"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
volumes: []
|
||||
initcontainers: []
|
||||
containers: []
|
||||
restartpolicy: ""
|
||||
terminationgraceperiodseconds: null
|
||||
activedeadlineseconds: null
|
||||
dnspolicy: ""
|
||||
nodeselector: {}
|
||||
serviceaccountname: ""
|
||||
deprecatedserviceaccount: ""
|
||||
automountserviceaccounttoken: null
|
||||
nodename: ""
|
||||
hostnetwork: false
|
||||
hostpid: false
|
||||
hostipc: false
|
||||
shareprocessnamespace: null
|
||||
securitycontext: null
|
||||
imagepullsecrets: []
|
||||
hostname: ""
|
||||
subdomain: ""
|
||||
affinity: null
|
||||
schedulername: ""
|
||||
tolerations: []
|
||||
hostaliases: []
|
||||
priorityclassname: ""
|
||||
priority: null
|
||||
dnsconfig: null
|
||||
readinessgates: []
|
||||
runtimeclassname: null
|
||||
enableservicelinks: null
|
||||
ttlsecondsafterfinished: null
|
||||
status:
|
||||
conditions: []
|
||||
starttime: "2018-12-14T10:36:43.326972-07:00"
|
||||
completiontime: "2018-12-14T10:36:43.326972-07:00"
|
||||
active: 0
|
||||
succeeded: 0
|
||||
failed: 0
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/kubernetes/pkg/kubectl/metricsutil"
|
||||
"k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
metricsapi "k8s.io/metrics/pkg/apis/metrics"
|
||||
versioned "k8s.io/metrics/pkg/client/clientset/versioned"
|
||||
)
|
||||
|
||||
// NA Not available
|
||||
const NA = "n/a"
|
||||
|
||||
var (
|
||||
conn connection
|
||||
supportedMetricsAPIVersions = []string{"v1beta1"}
|
||||
)
|
||||
|
||||
func init() {
|
||||
conn = &apiServer{}
|
||||
}
|
||||
|
||||
type (
|
||||
ApiGroup struct {
|
||||
Resource string
|
||||
Group, Kind, Version string
|
||||
Plural, Singular string
|
||||
Aliases []string
|
||||
}
|
||||
|
||||
// Collection of empty interfaces.
|
||||
Collection []interface{}
|
||||
|
||||
// Res K8s api server calls
|
||||
Res interface {
|
||||
Get(ns string, name string) (interface{}, error)
|
||||
List(ns string) (Collection, error)
|
||||
Delete(ns string, name string) error
|
||||
}
|
||||
|
||||
connection interface {
|
||||
configOrDie() *restclient.Config
|
||||
dialConfigOrDie() *clientcmdapi.Config
|
||||
dialOrDie() kubernetes.Interface
|
||||
dynDialOrDie() dynamic.Interface
|
||||
nsDialOrDie() dynamic.NamespaceableResourceInterface
|
||||
mxsDial() (*versioned.Clientset, error)
|
||||
heapsterDial() (*metricsutil.HeapsterMetricsClient, error)
|
||||
getClusterName() string
|
||||
setClusterName(n string)
|
||||
hasMetricsServer() bool
|
||||
}
|
||||
|
||||
apiServer struct {
|
||||
client kubernetes.Interface
|
||||
dClient dynamic.Interface
|
||||
csClient *clientset.Clientset
|
||||
nsClient dynamic.NamespaceableResourceInterface
|
||||
heapsterClient *metricsutil.HeapsterMetricsClient
|
||||
mxsClient *versioned.Clientset
|
||||
cluster string
|
||||
useMetricServer bool
|
||||
}
|
||||
)
|
||||
|
||||
func (a *apiServer) getClusterName() string {
|
||||
return a.cluster
|
||||
}
|
||||
|
||||
func (a *apiServer) setClusterName(n string) {
|
||||
a.cluster = n
|
||||
}
|
||||
|
||||
func (a *apiServer) hasMetricsServer() bool {
|
||||
return a.useMetricServer
|
||||
}
|
||||
|
||||
func (a *apiServer) dialConfigOrDie() *clientcmdapi.Config {
|
||||
c, err := clientcmd.NewDefaultPathOptions().GetStartingConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// DialOrDie returns a handle to api server or die.
|
||||
func (a *apiServer) dialOrDie() kubernetes.Interface {
|
||||
a.checkCurrentConfig()
|
||||
if a.client != nil {
|
||||
return a.client
|
||||
}
|
||||
|
||||
var err error
|
||||
if a.client, err = kubernetes.NewForConfig(a.configOrDie()); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return a.client
|
||||
}
|
||||
|
||||
func (a *apiServer) csDialOrDie() *clientset.Clientset {
|
||||
a.checkCurrentConfig()
|
||||
if a.csClient != nil {
|
||||
return a.csClient
|
||||
}
|
||||
|
||||
var cfg *rest.Config
|
||||
// cfg := clientcmd.NewNonInteractiveClientConfig(config, contextName, overrides, configAccess)
|
||||
var err error
|
||||
if a.csClient, err = clientset.NewForConfig(cfg); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return a.csClient
|
||||
}
|
||||
|
||||
// DynDial returns a handle to the api server.
|
||||
func (a *apiServer) dynDialOrDie() dynamic.Interface {
|
||||
a.checkCurrentConfig()
|
||||
if a.dClient != nil {
|
||||
return a.dClient
|
||||
}
|
||||
|
||||
var err error
|
||||
if a.dClient, err = dynamic.NewForConfig(a.configOrDie()); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return a.dClient
|
||||
}
|
||||
|
||||
func (a *apiServer) nsDialOrDie() dynamic.NamespaceableResourceInterface {
|
||||
a.checkCurrentConfig()
|
||||
if a.nsClient != nil {
|
||||
return a.nsClient
|
||||
}
|
||||
|
||||
a.nsClient = a.dynDialOrDie().Resource(schema.GroupVersionResource{
|
||||
Group: "apiextensions.k8s.io",
|
||||
Version: "v1beta1",
|
||||
Resource: "customresourcedefinitions",
|
||||
})
|
||||
return a.nsClient
|
||||
}
|
||||
|
||||
func (a *apiServer) heapsterDial() (*metricsutil.HeapsterMetricsClient, error) {
|
||||
a.checkCurrentConfig()
|
||||
if a.heapsterClient != nil {
|
||||
return a.heapsterClient, nil
|
||||
}
|
||||
|
||||
a.heapsterClient = metricsutil.NewHeapsterMetricsClient(
|
||||
a.dialOrDie().CoreV1(),
|
||||
metricsutil.DefaultHeapsterNamespace,
|
||||
metricsutil.DefaultHeapsterScheme,
|
||||
metricsutil.DefaultHeapsterService,
|
||||
metricsutil.DefaultHeapsterPort,
|
||||
)
|
||||
return a.heapsterClient, nil
|
||||
}
|
||||
|
||||
func (a *apiServer) mxsDial() (*versioned.Clientset, error) {
|
||||
a.checkCurrentConfig()
|
||||
if a.mxsClient != nil {
|
||||
return a.mxsClient, nil
|
||||
}
|
||||
|
||||
opts := clientcmd.NewDefaultPathOptions()
|
||||
dat, err := ioutil.ReadFile(opts.GetDefaultFilename())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rc, err := clientcmd.RESTConfigFromKubeConfig(dat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a.mxsClient, err = versioned.NewForConfig(rc)
|
||||
return a.mxsClient, err
|
||||
}
|
||||
|
||||
func (*apiServer) configOrDie() *restclient.Config {
|
||||
opts := clientcmd.NewDefaultPathOptions()
|
||||
cfg, err := clientcmd.BuildConfigFromFlags("", opts.GetDefaultFilename())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (a *apiServer) checkCurrentConfig() {
|
||||
cfg, err := clientcmd.NewDefaultPathOptions().GetStartingConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(a.getClusterName()) == 0 {
|
||||
a.setClusterName(cfg.CurrentContext)
|
||||
a.useMetricServer = a.supportsMxServer()
|
||||
return
|
||||
}
|
||||
|
||||
if a.getClusterName() != cfg.CurrentContext {
|
||||
a.reset()
|
||||
a.setClusterName(cfg.CurrentContext)
|
||||
a.useMetricServer = a.supportsMxServer()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *apiServer) reset() {
|
||||
a.client, a.dClient, a.nsClient = nil, nil, nil
|
||||
a.heapsterClient, a.mxsClient = nil, nil
|
||||
a.setClusterName("")
|
||||
}
|
||||
|
||||
func (a *apiServer) supportsMxServer() bool {
|
||||
apiGroups, err := a.dialOrDie().Discovery().ServerGroups()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, discoveredAPIGroup := range apiGroups.Groups {
|
||||
if discoveredAPIGroup.Name != metricsapi.GroupName {
|
||||
continue
|
||||
}
|
||||
for _, version := range discoveredAPIGroup.Versions {
|
||||
for _, supportedVersion := range supportedMetricsAPIVersions {
|
||||
if version.Version == supportedVersion {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package k8s
|
||||
|
||||
// Cluster manages a Kubernetes ClusterRole.
|
||||
type Cluster struct{}
|
||||
|
||||
// NewCluster instantiates a new ClusterRole.
|
||||
func NewCluster() *Cluster {
|
||||
return &Cluster{}
|
||||
}
|
||||
|
||||
// Version retrieves cluster git version.
|
||||
func (c *Cluster) Version() (string, error) {
|
||||
rev, err := conn.dialOrDie().Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return rev.GitVersion, nil
|
||||
}
|
||||
|
||||
// ClusterName retrieves cluster name.
|
||||
func (c *Cluster) ClusterName() string {
|
||||
return conn.getClusterName()
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ClusterRole represents a Kubernetes ClusterRole
|
||||
type ClusterRole struct{}
|
||||
|
||||
// NewClusterRole returns a new ClusterRole.
|
||||
func NewClusterRole() Res {
|
||||
return &ClusterRole{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*ClusterRole) Get(_, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().RbacV1().ClusterRoles().Get(n, opts)
|
||||
}
|
||||
|
||||
// List all ClusterRoles in a given namespace
|
||||
func (*ClusterRole) List(_ string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().RbacV1().ClusterRoles().List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a ClusterRole
|
||||
func (*ClusterRole) Delete(_, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().RbacV1().ClusterRoles().Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ClusterRoleBinding represents a Kubernetes ClusterRoleBinding
|
||||
type ClusterRoleBinding struct{}
|
||||
|
||||
// NewClusterRoleBinding returns a new ClusterRoleBinding.
|
||||
func NewClusterRoleBinding() Res {
|
||||
return &ClusterRoleBinding{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*ClusterRoleBinding) Get(_, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().RbacV1().ClusterRoleBindings().Get(n, opts)
|
||||
}
|
||||
|
||||
// List all ClusterRoleBindings in a given namespace
|
||||
func (*ClusterRoleBinding) List(_ string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().RbacV1().ClusterRoleBindings().List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a ClusterRoleBinding
|
||||
func (*ClusterRoleBinding) Delete(_, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().RbacV1().ClusterRoleBindings().Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ConfigMap represents a Kubernetes ConfigMap
|
||||
type ConfigMap struct{}
|
||||
|
||||
// NewConfigMap returns a new ConfigMap.
|
||||
func NewConfigMap() Res {
|
||||
return &ConfigMap{}
|
||||
}
|
||||
|
||||
// Get a ConfigMap.
|
||||
func (c *ConfigMap) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().CoreV1().ConfigMaps(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all ConfigMaps in a given namespace
|
||||
func (c *ConfigMap) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().CoreV1().ConfigMaps(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a ConfigMap
|
||||
func (c *ConfigMap) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().CoreV1().ConfigMaps(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
// ContextRes represents a kubernetes clusters configurations.
|
||||
type ContextRes interface {
|
||||
Res
|
||||
Switch(n string) error
|
||||
}
|
||||
|
||||
// NamedContext represents a named cluster context.
|
||||
type NamedContext struct {
|
||||
Name string
|
||||
Context *api.Context
|
||||
}
|
||||
|
||||
// CurrentCluster return active cluster name
|
||||
func (c *NamedContext) CurrentCluster() string {
|
||||
return conn.getClusterName()
|
||||
}
|
||||
|
||||
// Context represents a Kubernetes Context.
|
||||
type Context struct{}
|
||||
|
||||
// NewContext returns a new Context.
|
||||
func NewContext() Res {
|
||||
return &Context{}
|
||||
}
|
||||
|
||||
// Get a Context.
|
||||
func (*Context) Get(_, n string) (interface{}, error) {
|
||||
return &NamedContext{Name: n, Context: conn.dialConfigOrDie().Contexts[n]}, nil
|
||||
}
|
||||
|
||||
// List all Contexts in a given namespace
|
||||
func (*Context) List(string) (Collection, error) {
|
||||
conn := conn.dialConfigOrDie()
|
||||
|
||||
cc := make([]interface{}, 0, len(conn.Contexts))
|
||||
for k, v := range conn.Contexts {
|
||||
cc = append(cc, &NamedContext{k, v})
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a Context
|
||||
func (*Context) Delete(_, n string) error {
|
||||
conn := conn.dialConfigOrDie()
|
||||
|
||||
if conn.CurrentContext == n {
|
||||
return fmt.Errorf("trying to delete your current context %s", n)
|
||||
}
|
||||
|
||||
acc := clientcmd.NewDefaultPathOptions()
|
||||
|
||||
delete(conn.Contexts, n)
|
||||
return clientcmd.ModifyConfig(acc, *conn, true)
|
||||
}
|
||||
|
||||
// Switch cluster Context.
|
||||
func (*Context) Switch(n string) error {
|
||||
conn := conn.dialConfigOrDie()
|
||||
|
||||
conn.CurrentContext = n
|
||||
acc := clientcmd.NewDefaultPathOptions()
|
||||
return clientcmd.ModifyConfig(acc, *conn, true)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// CRD represents a Kubernetes CRD
|
||||
type CRD struct{}
|
||||
|
||||
// NewCRD returns a new CRD.
|
||||
func NewCRD() Res {
|
||||
return &CRD{}
|
||||
}
|
||||
|
||||
// Get a CRD.
|
||||
func (*CRD) Get(_, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.nsDialOrDie().Get(n, opts)
|
||||
}
|
||||
|
||||
// List all CRDs in a given namespace
|
||||
func (*CRD) List(string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.nsDialOrDie().List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a CRD
|
||||
func (*CRD) Delete(_, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.nsDialOrDie().Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// CronJob represents a Kubernetes CronJob
|
||||
type CronJob struct{}
|
||||
|
||||
// NewCronJob returns a new CronJob.
|
||||
func NewCronJob() Res {
|
||||
return &CronJob{}
|
||||
}
|
||||
|
||||
// Get a CronJob.
|
||||
func (c *CronJob) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().BatchV1beta1().CronJobs(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all CronJobs in a given namespace
|
||||
func (c *CronJob) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().BatchV1beta1().CronJobs(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a CronJob
|
||||
func (c *CronJob) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().BatchV1beta1().CronJobs(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Deployment represents a Kubernetes Deployment
|
||||
type Deployment struct{}
|
||||
|
||||
// NewDeployment returns a new Deployment.
|
||||
func NewDeployment() Res {
|
||||
return &Deployment{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*Deployment) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().Apps().Deployments(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all Deployments in a given namespace
|
||||
func (*Deployment) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().Apps().Deployments(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a Deployment
|
||||
func (*Deployment) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().Apps().Deployments(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// DaemonSet represents a Kubernetes DaemonSet
|
||||
type DaemonSet struct{}
|
||||
|
||||
// NewDaemonSet returns a new DaemonSet.
|
||||
func NewDaemonSet() Res {
|
||||
return &DaemonSet{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*DaemonSet) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().ExtensionsV1beta1().DaemonSets(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all DaemonSets in a given namespace
|
||||
func (*DaemonSet) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().ExtensionsV1beta1().DaemonSets(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a DaemonSet
|
||||
func (*DaemonSet) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().ExtensionsV1beta1().DaemonSets(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Endpoints represents a Kubernetes Endpoints
|
||||
type Endpoints struct{}
|
||||
|
||||
// NewEndpoints returns a new Endpoints.
|
||||
func NewEndpoints() Res {
|
||||
return &Endpoints{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*Endpoints) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().CoreV1().Endpoints(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all Endpointss in a given namespace
|
||||
func (*Endpoints) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().CoreV1().Endpoints(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a Endpoints
|
||||
func (*Endpoints) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().CoreV1().Endpoints(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Event represents a Kubernetes Event
|
||||
type Event struct{}
|
||||
|
||||
// NewEvent returns a new Event.
|
||||
func NewEvent() Res {
|
||||
return &Event{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*Event) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().CoreV1().Events(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all services in a given namespace
|
||||
func (*Event) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().CoreV1().Events(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a service
|
||||
func (*Event) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().CoreV1().Events(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// HPA represents am HorizontalPodAutoscaler.
|
||||
type HPA struct{}
|
||||
|
||||
// NewHPA returns a new HPA.
|
||||
func NewHPA() Res {
|
||||
return &HPA{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*HPA) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().Autoscaling().HorizontalPodAutoscalers(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all services in a given namespace
|
||||
func (*HPA) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().Autoscaling().HorizontalPodAutoscalers(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a service
|
||||
func (*HPA) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().Autoscaling().HorizontalPodAutoscalers(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Ingress represents a Kubernetes Ingress
|
||||
type Ingress struct{}
|
||||
|
||||
// NewIngress returns a new Ingress.
|
||||
func NewIngress() Res {
|
||||
return &Ingress{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*Ingress) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().ExtensionsV1beta1().Ingresses(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all Ingresss in a given namespace
|
||||
func (*Ingress) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().ExtensionsV1beta1().Ingresses(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a Ingress
|
||||
func (*Ingress) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().ExtensionsV1beta1().Ingresses(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Job represents a Kubernetes Job
|
||||
type Job struct{}
|
||||
|
||||
// NewJob returns a new Job.
|
||||
func NewJob() Res {
|
||||
return &Job{}
|
||||
}
|
||||
|
||||
// Get a Job.
|
||||
func (c *Job) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().BatchV1().Jobs(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all Jobs in a given namespace
|
||||
func (c *Job) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().BatchV1().Jobs(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a Job
|
||||
func (c *Job) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().BatchV1().Jobs(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"path"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
metricsapi "k8s.io/metrics/pkg/apis/metrics"
|
||||
metricsV1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
)
|
||||
|
||||
type (
|
||||
// MetricsServer serves cluster metrics for nodes and pods.
|
||||
MetricsServer struct{}
|
||||
|
||||
// Metric tracks resource metrics.
|
||||
Metric struct {
|
||||
CPU string
|
||||
Mem string
|
||||
AvailCPU string
|
||||
AvailMem string
|
||||
}
|
||||
)
|
||||
|
||||
// NewMetricsServer return a metric server instance.
|
||||
func NewMetricsServer() *MetricsServer {
|
||||
return &MetricsServer{}
|
||||
}
|
||||
|
||||
// NodeMetrics retrieves all nodes metrics
|
||||
func (m *MetricsServer) NodeMetrics() (Metric, error) {
|
||||
var mx Metric
|
||||
|
||||
opts := metav1.ListOptions{}
|
||||
nn, err := conn.dialOrDie().CoreV1().Nodes().List(opts)
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
nods := make([]string, len(nn.Items))
|
||||
var maxCPU, maxMem float64
|
||||
for i, n := range nn.Items {
|
||||
nods[i] = n.Name
|
||||
c := n.Status.Allocatable["cpu"]
|
||||
maxCPU += float64(c.MilliValue())
|
||||
m := n.Status.Allocatable["memory"]
|
||||
maxMem += float64(m.Value() / (1024 * 1024))
|
||||
}
|
||||
|
||||
mm, err := m.getNodeMetrics()
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
||||
var cpu, mem float64
|
||||
for _, n := range nods {
|
||||
for _, m := range mm.Items {
|
||||
if m.Name == n {
|
||||
cpu += float64(m.Usage.Cpu().MilliValue())
|
||||
mem += float64(m.Usage.Memory().Value() / (1024 * 1024))
|
||||
}
|
||||
}
|
||||
}
|
||||
mx = Metric{
|
||||
CPU: fmt.Sprintf("%0.f%%", math.Round((cpu/maxCPU)*100)),
|
||||
Mem: fmt.Sprintf("%0.f%%", math.Round((mem/maxMem)*100)),
|
||||
}
|
||||
return mx, nil
|
||||
}
|
||||
|
||||
// PodMetrics retrieves all pods metrics
|
||||
func (m *MetricsServer) PodMetrics() (map[string]Metric, error) {
|
||||
mx := map[string]Metric{}
|
||||
|
||||
mm, err := m.getPodMetrics()
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
||||
for _, m := range mm.Items {
|
||||
var cpu, mem int64
|
||||
for _, c := range m.Containers {
|
||||
cpu += c.Usage.Cpu().MilliValue()
|
||||
mem += c.Usage.Memory().Value() / (1024 * 1024)
|
||||
}
|
||||
pa := path.Join(m.Namespace, m.Name)
|
||||
mx[pa] = Metric{CPU: fmt.Sprintf("%dm", cpu), Mem: fmt.Sprintf("%dMi", mem)}
|
||||
}
|
||||
return mx, nil
|
||||
}
|
||||
|
||||
// PerNodeMetrics retrieves all nodes metrics
|
||||
func (m *MetricsServer) PerNodeMetrics(nn []v1.Node) (map[string]Metric, error) {
|
||||
log.Println("Getting per node metrics...")
|
||||
mx := map[string]Metric{}
|
||||
|
||||
mm, err := m.getNodeMetrics()
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
|
||||
for _, n := range nn {
|
||||
acpu := n.Status.Allocatable["cpu"]
|
||||
amem := n.Status.Allocatable["memory"]
|
||||
var cpu, mem int64
|
||||
for _, m := range mm.Items {
|
||||
if m.Name == n.Name {
|
||||
cpu += m.Usage.Cpu().MilliValue()
|
||||
mem += m.Usage.Memory().Value() / (1024 * 1024)
|
||||
}
|
||||
}
|
||||
mx[n.Name] = Metric{
|
||||
CPU: fmt.Sprintf("%dm", cpu),
|
||||
Mem: fmt.Sprintf("%dMi", mem),
|
||||
AvailCPU: fmt.Sprintf("%dm", acpu.MilliValue()),
|
||||
AvailMem: fmt.Sprintf("%dMi", amem.Value()/(1024*1024)),
|
||||
}
|
||||
}
|
||||
return mx, nil
|
||||
}
|
||||
|
||||
func (m *MetricsServer) getPodMetrics() (*metricsapi.PodMetricsList, error) {
|
||||
if conn.hasMetricsServer() {
|
||||
return m.podMetricsViaService()
|
||||
}
|
||||
selector := labels.Everything()
|
||||
|
||||
var mx *metricsapi.PodMetricsList
|
||||
conn, err := conn.heapsterDial()
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
return conn.GetPodMetrics("", "", true, selector)
|
||||
}
|
||||
|
||||
func (m *MetricsServer) getNodeMetrics() (*metricsapi.NodeMetricsList, error) {
|
||||
if conn.hasMetricsServer() {
|
||||
return m.nodeMetricsViaService()
|
||||
}
|
||||
selector := labels.Everything()
|
||||
|
||||
var mx *metricsapi.NodeMetricsList
|
||||
conn, err := conn.heapsterDial()
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
return conn.GetNodeMetrics("", selector.String())
|
||||
}
|
||||
|
||||
func (*MetricsServer) nodeMetricsViaService() (*metricsapi.NodeMetricsList, error) {
|
||||
var mx *metricsapi.NodeMetricsList
|
||||
clt, err := conn.mxsDial()
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
selector := labels.Everything()
|
||||
var versionedMetrics *metricsV1beta1api.NodeMetricsList
|
||||
mc := clt.Metrics()
|
||||
nm := mc.NodeMetricses()
|
||||
versionedMetrics, err = nm.List(metav1.ListOptions{LabelSelector: selector.String()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metrics := &metricsapi.NodeMetricsList{}
|
||||
err = metricsV1beta1api.Convert_v1beta1_NodeMetricsList_To_metrics_NodeMetricsList(versionedMetrics, metrics, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
func (*MetricsServer) podMetricsViaService() (*metricsapi.PodMetricsList, error) {
|
||||
var mx *metricsapi.PodMetricsList
|
||||
clt, err := conn.mxsDial()
|
||||
if err != nil {
|
||||
return mx, err
|
||||
}
|
||||
selector := labels.Everything()
|
||||
var versionedMetrics *metricsV1beta1api.PodMetricsList
|
||||
mc := clt.Metrics()
|
||||
nm := mc.PodMetricses("")
|
||||
versionedMetrics, err = nm.List(metav1.ListOptions{LabelSelector: selector.String()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metrics := &metricsapi.PodMetricsList{}
|
||||
err = metricsV1beta1api.Convert_v1beta1_PodMetricsList_To_metrics_PodMetricsList(versionedMetrics, metrics, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return metrics, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Node represents a Kubernetes service
|
||||
type Node struct{}
|
||||
|
||||
// NewNode returns a new Node.
|
||||
func NewNode() Res {
|
||||
return &Node{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*Node) Get(_, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().CoreV1().Nodes().Get(n, opts)
|
||||
}
|
||||
|
||||
// List all services in a given namespace
|
||||
func (*Node) List(_ string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().CoreV1().Nodes().List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a service
|
||||
func (*Node) Delete(_, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().CoreV1().Nodes().Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Namespace represents a Kubernetes service
|
||||
type Namespace struct{}
|
||||
|
||||
// NewNamespace returns a new Namespace.
|
||||
func NewNamespace() Res {
|
||||
return &Namespace{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*Namespace) Get(_, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().CoreV1().Namespaces().Get(n, opts)
|
||||
}
|
||||
|
||||
// List all services in a given namespace
|
||||
func (*Namespace) List(_ string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().CoreV1().Namespaces().List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a service
|
||||
func (*Namespace) Delete(_, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().CoreV1().Namespaces().Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
const defaultTailLines = 10
|
||||
|
||||
// PodRes represents a K8s pod resource.
|
||||
type PodRes interface {
|
||||
Res
|
||||
Containers(ns, n string) ([]string, error)
|
||||
Logs(ns, n, co string) *restclient.Request
|
||||
}
|
||||
|
||||
// Pod represents a Kubernetes service
|
||||
type Pod struct{}
|
||||
|
||||
// NewPod returns a new Pod.
|
||||
func NewPod() Res {
|
||||
return &Pod{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*Pod) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().CoreV1().Pods(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all services in a given namespace
|
||||
func (*Pod) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().CoreV1().Pods(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a service
|
||||
func (*Pod) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().CoreV1().Pods(ns).Delete(n, &opts)
|
||||
}
|
||||
|
||||
// Containers returns all container names on pod
|
||||
func (*Pod) Containers(ns, n string) ([]string, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
cc := []string{}
|
||||
po, err := conn.dialOrDie().CoreV1().Pods(ns).Get(n, opts)
|
||||
if err != nil {
|
||||
return cc, err
|
||||
}
|
||||
|
||||
for _, c := range po.Spec.Containers {
|
||||
cc = append(cc, c.Name)
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Logs fetch container logs for a given pod and container.
|
||||
func (*Pod) Logs(ns, n, co string) *restclient.Request {
|
||||
tl := int64(defaultTailLines)
|
||||
opts := &v1.PodLogOptions{
|
||||
Container: co,
|
||||
Follow: true,
|
||||
TailLines: &tl,
|
||||
}
|
||||
return conn.dialOrDie().CoreV1().Pods(ns).GetLogs(n, opts)
|
||||
}
|
||||
|
||||
// Events retrieved pod's events.
|
||||
func (*Pod) Events(ns, n string) (*v1.EventList, error) {
|
||||
e := conn.dialOrDie().Core().Events(ns)
|
||||
sel := e.GetFieldSelector(&n, &ns, nil, nil)
|
||||
opts := metav1.ListOptions{FieldSelector: sel.String()}
|
||||
ee, err := e.List(opts)
|
||||
return ee, err
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// PV represents a Kubernetes service
|
||||
type PV struct{}
|
||||
|
||||
// NewPV returns a new PV.
|
||||
func NewPV() Res {
|
||||
return &PV{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*PV) Get(_, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().CoreV1().PersistentVolumes().Get(n, opts)
|
||||
}
|
||||
|
||||
// List all services in a given namespace
|
||||
func (*PV) List(_ string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().CoreV1().PersistentVolumes().List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a service
|
||||
func (*PV) Delete(_, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().CoreV1().PersistentVolumes().Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// PVC represents a Kubernetes service
|
||||
type PVC struct{}
|
||||
|
||||
// NewPVC returns a new PVC.
|
||||
func NewPVC() Res {
|
||||
return &PVC{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*PVC) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().CoreV1().PersistentVolumeClaims(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all services in a given namespace
|
||||
func (*PVC) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().CoreV1().PersistentVolumeClaims(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a service
|
||||
func (*PVC) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().CoreV1().PersistentVolumeClaims(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// Resource represents a Kubernetes Resource
|
||||
type Resource struct {
|
||||
group, version, name string
|
||||
}
|
||||
|
||||
// NewResource returns a new Resource.
|
||||
func NewResource(group, version, name string) Res {
|
||||
return &Resource{group: group, version: version, name: name}
|
||||
}
|
||||
|
||||
// GetInfo returns info about apigroup.
|
||||
func (r *Resource) GetInfo() (string, string, string) {
|
||||
return r.group, r.version, r.name
|
||||
}
|
||||
|
||||
func (r *Resource) base() dynamic.NamespaceableResourceInterface {
|
||||
g := schema.GroupVersionResource{
|
||||
Group: r.group,
|
||||
Version: r.version,
|
||||
Resource: r.name,
|
||||
}
|
||||
return conn.dynDialOrDie().Resource(g)
|
||||
}
|
||||
|
||||
// Get a Resource.
|
||||
func (r *Resource) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return r.base().Namespace(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all Resources in a given namespace
|
||||
func (r *Resource) List(ns string) (Collection, error) {
|
||||
obj, err := r.listAll(ns, r.name)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
return Collection{obj.(*metav1beta1.Table)}, nil
|
||||
}
|
||||
|
||||
// Delete a Resource
|
||||
func (r *Resource) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return r.base().Namespace(ns).Delete(n, &opts)
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func (r *Resource) getClient() *rest.RESTClient {
|
||||
gv := schema.GroupVersion{Group: r.group, Version: r.version}
|
||||
codecs, _ := r.codecs()
|
||||
crConfig := *conn.configOrDie()
|
||||
crConfig.GroupVersion = &gv
|
||||
crConfig.APIPath = "/apis"
|
||||
if len(r.group) == 0 {
|
||||
crConfig.APIPath = "/api"
|
||||
}
|
||||
crConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: codecs}
|
||||
crRestClient, err := rest.RESTClientFor(&crConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return crRestClient
|
||||
}
|
||||
|
||||
func (r *Resource) codecs() (serializer.CodecFactory, runtime.ParameterCodec) {
|
||||
scheme := runtime.NewScheme()
|
||||
gv := schema.GroupVersion{Group: r.group, Version: r.version}
|
||||
metav1.AddToGroupVersion(scheme, gv)
|
||||
scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{})
|
||||
scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{})
|
||||
codecs := serializer.NewCodecFactory(scheme)
|
||||
return codecs, runtime.NewParameterCodec(scheme)
|
||||
}
|
||||
|
||||
func (r *Resource) listAll(ns, n string) (runtime.Object, error) {
|
||||
group := metav1beta1.GroupName
|
||||
version := metav1beta1.SchemeGroupVersion.Version
|
||||
a := fmt.Sprintf("application/json;as=Table;v=%s;g=%s, application/json", version, group)
|
||||
|
||||
_, codec := r.codecs()
|
||||
return r.getClient().Get().
|
||||
SetHeader("Accept", a).
|
||||
Namespace(ns).
|
||||
Resource(n).
|
||||
VersionedParams(&metav1beta1.TableOptions{}, codec).
|
||||
Do().
|
||||
Get()
|
||||
}
|
||||
|
||||
func (r *Resource) getOne(ns, n string) (runtime.Object, error) {
|
||||
group := metav1beta1.GroupName
|
||||
version := metav1beta1.SchemeGroupVersion.Version
|
||||
a := fmt.Sprintf("application/json;as=Table;v=%s;g=%s, application/json", version, group)
|
||||
|
||||
_, codec := r.codecs()
|
||||
return r.getClient().Get().
|
||||
SetHeader("Accept", a).
|
||||
Namespace(ns).
|
||||
Resource(n).
|
||||
VersionedParams(&metav1beta1.TableOptions{}, codec).
|
||||
Do().
|
||||
Get()
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
// rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Role represents a Kubernetes service
|
||||
type Role struct{}
|
||||
|
||||
// NewRole returns a new Role.
|
||||
func NewRole() Res {
|
||||
return &Role{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*Role) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().RbacV1().Roles(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all services in a given namespace
|
||||
func (*Role) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().RbacV1().Roles(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a service
|
||||
func (*Role) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().RbacV1().Roles(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package k8s
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// RoleBinding represents a Kubernetes service
|
||||
type RoleBinding struct{}
|
||||
|
||||
// NewRoleBinding returns a new RoleBinding.
|
||||
func NewRoleBinding() Res {
|
||||
return &RoleBinding{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*RoleBinding) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().RbacV1().RoleBindings(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all services in a given namespace
|
||||
func (*RoleBinding) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().RbacV1().RoleBindings(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a service
|
||||
func (*RoleBinding) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().RbacV1().RoleBindings(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ReplicaSet represents a Kubernetes service
|
||||
type ReplicaSet struct{}
|
||||
|
||||
// NewReplicaSet returns a new ReplicaSet.
|
||||
func NewReplicaSet() Res {
|
||||
return &ReplicaSet{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*ReplicaSet) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().Apps().ReplicaSets(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all services in a given namespace
|
||||
func (*ReplicaSet) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().Apps().ReplicaSets(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a service
|
||||
func (*ReplicaSet) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().Apps().ReplicaSets(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ServiceAccount manages a Kubernetes ServiceAccount.
|
||||
type ServiceAccount struct{}
|
||||
|
||||
// NewServiceAccount instantiates a new ServiceAccount.
|
||||
func NewServiceAccount() Res {
|
||||
return &ServiceAccount{}
|
||||
}
|
||||
|
||||
// Get a ServiceAccount
|
||||
func (*ServiceAccount) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
o, err := conn.dialOrDie().CoreV1().ServiceAccounts(ns).Get(n, opts)
|
||||
if err != nil {
|
||||
return o, err
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// List all ServiceAccounts in a given namespace
|
||||
func (*ServiceAccount) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().CoreV1().ServiceAccounts(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
return cc, nil
|
||||
|
||||
}
|
||||
|
||||
// Delete a ServiceAccount
|
||||
func (*ServiceAccount) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().CoreV1().ServiceAccounts(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Secret represents a Kubernetes Secret
|
||||
type Secret struct{}
|
||||
|
||||
// NewSecret returns a new Secret.
|
||||
func NewSecret() Res {
|
||||
return &Secret{}
|
||||
}
|
||||
|
||||
// Get a Secret.
|
||||
func (c *Secret) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().CoreV1().Secrets(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all Secrets in a given namespace
|
||||
func (c *Secret) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().CoreV1().Secrets(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a Secret
|
||||
func (c *Secret) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().CoreV1().Secrets(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// StatefulSet manages a Kubernetes StatefulSet.
|
||||
type StatefulSet struct{}
|
||||
|
||||
// NewStatefulSet instantiates a new StatefulSet.
|
||||
func NewStatefulSet() Res {
|
||||
return &StatefulSet{}
|
||||
}
|
||||
|
||||
// Get a StatefulSet
|
||||
func (*StatefulSet) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
o, err := conn.dialOrDie().AppsV1().StatefulSets(ns).Get(n, opts)
|
||||
if err != nil {
|
||||
return o, err
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// List all StatefulSets in a given namespace
|
||||
func (*StatefulSet) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().AppsV1().StatefulSets(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a StatefulSet
|
||||
func (*StatefulSet) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().AppsV1().StatefulSets(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Service represents a Kubernetes Service
|
||||
type Service struct{}
|
||||
|
||||
// NewService returns a new Service.
|
||||
func NewService() Res {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
// Get a service.
|
||||
func (*Service) Get(ns, n string) (interface{}, error) {
|
||||
opts := metav1.GetOptions{}
|
||||
return conn.dialOrDie().CoreV1().Services(ns).Get(n, opts)
|
||||
}
|
||||
|
||||
// List all Services in a given namespace
|
||||
func (*Service) List(ns string) (Collection, error) {
|
||||
opts := metav1.ListOptions{}
|
||||
|
||||
rr, err := conn.dialOrDie().CoreV1().Services(ns).List(opts)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
cc := make(Collection, len(rr.Items))
|
||||
for i, r := range rr.Items {
|
||||
cc[i] = r
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Delete a Service
|
||||
func (*Service) Delete(ns, n string) error {
|
||||
opts := metav1.DeleteOptions{}
|
||||
return conn.dialOrDie().CoreV1().Services(ns).Delete(n, &opts)
|
||||
}
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
const (
|
||||
// GetAccess set if resource can be fetched.
|
||||
GetAccess = 1 << iota
|
||||
// ListAccess set if resource can be listed.
|
||||
ListAccess
|
||||
// EditAccess set if resource can be edited.
|
||||
EditAccess
|
||||
// DeleteAccess set if resource can be deleted.
|
||||
DeleteAccess
|
||||
// ViewAccess set if resource can be viewed.
|
||||
ViewAccess
|
||||
// NamespaceAccess set if namespaced resource.
|
||||
NamespaceAccess
|
||||
// DescribeAccess set if resource can be described.
|
||||
DescribeAccess
|
||||
// SwitchAccess set if resource can be switched (Context).
|
||||
SwitchAccess
|
||||
|
||||
// CRUDAccess Verbs.
|
||||
CRUDAccess = GetAccess | ListAccess | DeleteAccess | ViewAccess | EditAccess
|
||||
|
||||
// AllVerbsAccess super powers.
|
||||
AllVerbsAccess = CRUDAccess | NamespaceAccess
|
||||
)
|
||||
|
||||
type (
|
||||
// SortFn provides for sorting items in list.
|
||||
SortFn func([]string)
|
||||
|
||||
// RowEvent represents a call for action after a resource reconciliation.
|
||||
// Tracks whether a resource got added, deleted or updated.
|
||||
RowEvent struct {
|
||||
Action watch.EventType
|
||||
Fields Row
|
||||
Deltas Row
|
||||
}
|
||||
|
||||
// RowEvents tracks resource update events.
|
||||
RowEvents map[string]*RowEvent
|
||||
|
||||
// Properties a collection of extra properties on a K8s resource.
|
||||
Properties map[string]interface{}
|
||||
|
||||
// TableData tracks a K8s resource for tabular display.
|
||||
TableData struct {
|
||||
Header Row
|
||||
Rows RowEvents
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// List protocol to display and update a collection of resources
|
||||
List interface {
|
||||
Data() TableData
|
||||
Resource() Resource
|
||||
GetNamespace() string
|
||||
SetNamespace(string)
|
||||
Reconcile() error
|
||||
Describe(pa string) (Properties, error)
|
||||
GetName() string
|
||||
Access(flag int) bool
|
||||
HasXRay() bool
|
||||
SortFn() SortFn
|
||||
}
|
||||
|
||||
// Columnar tracks resources that can be diplayed in a tabular fashion.
|
||||
Columnar interface {
|
||||
Header(ns string) Row
|
||||
Fields(ns string) Row
|
||||
ExtFields() Properties
|
||||
Name() string
|
||||
}
|
||||
|
||||
// Row represents a collection of string fields.
|
||||
Row []string
|
||||
|
||||
// Columnars a collection of columnars.
|
||||
Columnars []Columnar
|
||||
|
||||
// MxColumnar tracks resource metrics.
|
||||
MxColumnar interface {
|
||||
Columnar
|
||||
Metrics() k8s.Metric
|
||||
SetMetrics(k8s.Metric)
|
||||
}
|
||||
|
||||
// Resource tracks generic Kubernetes resources.
|
||||
Resource interface {
|
||||
NewInstance(interface{}) Columnar
|
||||
Get(path string) (Columnar, error)
|
||||
List(ns string) (Columnars, error)
|
||||
Delete(path string) error
|
||||
Marshal(pa string) (string, error)
|
||||
Header(ns string) Row
|
||||
}
|
||||
|
||||
list struct {
|
||||
namespace, name string
|
||||
verbs int
|
||||
xray bool
|
||||
api Resource
|
||||
cache RowEvents
|
||||
sortFn func([]string)
|
||||
}
|
||||
)
|
||||
|
||||
func newRowEvent(a watch.EventType, f, d Row) *RowEvent {
|
||||
return &RowEvent{Action: a, Fields: f, Deltas: d}
|
||||
}
|
||||
|
||||
func newList(ns, name string, api Resource, v int) *list {
|
||||
return &list{
|
||||
namespace: ns,
|
||||
name: name,
|
||||
verbs: v,
|
||||
api: api,
|
||||
cache: RowEvents{},
|
||||
}
|
||||
}
|
||||
|
||||
func (l *list) SortFn() SortFn {
|
||||
if l.sortFn == nil {
|
||||
return sort.Strings
|
||||
}
|
||||
return l.sortFn
|
||||
}
|
||||
|
||||
func (l *list) HasXRay() bool {
|
||||
return l.xray
|
||||
}
|
||||
|
||||
// Access check access control on a given resource.
|
||||
func (l *list) Access(f int) bool {
|
||||
return l.verbs&f == f
|
||||
}
|
||||
|
||||
// GetNamespace associated with the resource.
|
||||
func (l *list) GetNamespace() string {
|
||||
if !l.Access(NamespaceAccess) {
|
||||
l.namespace = NotNamespaced
|
||||
}
|
||||
return l.namespace
|
||||
}
|
||||
|
||||
// SetNamespace updates the namespace on the list. Default ns is "" for all
|
||||
// namespaces.
|
||||
func (l *list) SetNamespace(n string) {
|
||||
l.cache = RowEvents{}
|
||||
if l.Access(NamespaceAccess) {
|
||||
l.namespace = n
|
||||
}
|
||||
}
|
||||
|
||||
// GetName returns the kubernetes resource name.
|
||||
func (l *list) GetName() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
// Resource returns a resource api connection.
|
||||
func (l *list) Resource() Resource {
|
||||
return l.api
|
||||
}
|
||||
|
||||
// Cache tracks previous resource state.
|
||||
func (l *list) Data() TableData {
|
||||
return TableData{
|
||||
Header: l.api.Header(l.namespace),
|
||||
Rows: l.cache,
|
||||
Namespace: l.namespace,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *list) Describe(pa string) (Properties, error) {
|
||||
var p Properties
|
||||
i, err := l.api.Get(pa)
|
||||
if err != nil {
|
||||
return p, err
|
||||
}
|
||||
|
||||
return i.ExtFields(), nil
|
||||
}
|
||||
|
||||
// Reconcile previous vs current state and emits delta events.
|
||||
func (l *list) Reconcile() error {
|
||||
var (
|
||||
items Columnars
|
||||
err error
|
||||
)
|
||||
|
||||
if items, err = l.api.List(l.namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(l.cache) == 0 {
|
||||
for _, i := range items {
|
||||
ff := i.Fields(l.namespace)
|
||||
l.cache[i.Name()] = newRowEvent(New, ff, make(Row, len(ff)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
kk := make([]string, 0, len(items))
|
||||
for _, i := range items {
|
||||
a := watch.Added
|
||||
ff := i.Fields(l.namespace)
|
||||
dd := make(Row, len(ff))
|
||||
kk = append(kk, i.Name())
|
||||
if evt, ok := l.cache[i.Name()]; ok {
|
||||
f1, f2 := evt.Fields[:len(evt.Fields)-2], ff[:len(ff)-2]
|
||||
a = Unchanged
|
||||
if !reflect.DeepEqual(f1, f2) {
|
||||
for i, f := range f1 {
|
||||
if f != f2[i] {
|
||||
dd[i] = f
|
||||
}
|
||||
}
|
||||
a = watch.Modified
|
||||
}
|
||||
}
|
||||
l.cache[i.Name()] = newRowEvent(a, ff, dd)
|
||||
}
|
||||
|
||||
// Check for deletions!
|
||||
for k := range l.cache {
|
||||
var found bool
|
||||
for _, key := range kk {
|
||||
if k == key {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
delete(l.cache, k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
// Code generated by pegomock. DO NOT EDIT.
|
||||
// Source: github.com/k8sland/k9s/resource (interfaces: Caller)
|
||||
|
||||
package resource_test
|
||||
|
||||
import (
|
||||
k8s "github.com/k8sland/k9s/resource/k8s"
|
||||
pegomock "github.com/petergtz/pegomock"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MockCaller struct {
|
||||
fail func(message string, callerSkip ...int)
|
||||
}
|
||||
|
||||
func NewMockCaller() *MockCaller {
|
||||
return &MockCaller{fail: pegomock.GlobalFailHandler}
|
||||
}
|
||||
|
||||
func (mock *MockCaller) Delete(_param0 string, _param1 string) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockCaller().")
|
||||
}
|
||||
params := []pegomock.Param{_param0, _param1}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Delete", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(error)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockCaller) Get(_param0 string, _param1 string) (interface{}, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockCaller().")
|
||||
}
|
||||
params := []pegomock.Param{_param0, _param1}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Get", params, []reflect.Type{reflect.TypeOf((*interface{})(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 interface{}
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(interface{})
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockCaller) List(_param0 string) (k8s.Collection, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockCaller().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("List", params, []reflect.Type{reflect.TypeOf((*k8s.Collection)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 k8s.Collection
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(k8s.Collection)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockCaller) VerifyWasCalledOnce() *VerifierCaller {
|
||||
return &VerifierCaller{
|
||||
mock: mock,
|
||||
invocationCountMatcher: pegomock.Times(1),
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockCaller) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierCaller {
|
||||
return &VerifierCaller{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockCaller) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierCaller {
|
||||
return &VerifierCaller{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
inOrderContext: inOrderContext,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockCaller) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierCaller {
|
||||
return &VerifierCaller{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type VerifierCaller struct {
|
||||
mock *MockCaller
|
||||
invocationCountMatcher pegomock.Matcher
|
||||
inOrderContext *pegomock.InOrderContext
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierCaller) Delete(_param0 string, _param1 string) *Caller_Delete_OngoingVerification {
|
||||
params := []pegomock.Param{_param0, _param1}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Delete", params, verifier.timeout)
|
||||
return &Caller_Delete_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type Caller_Delete_OngoingVerification struct {
|
||||
mock *MockCaller
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *Caller_Delete_OngoingVerification) GetCapturedArguments() (string, string) {
|
||||
_param0, _param1 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1], _param1[len(_param1)-1]
|
||||
}
|
||||
|
||||
func (c *Caller_Delete_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
_param1 = make([]string, len(params[1]))
|
||||
for u, param := range params[1] {
|
||||
_param1[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierCaller) Get(_param0 string, _param1 string) *Caller_Get_OngoingVerification {
|
||||
params := []pegomock.Param{_param0, _param1}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Get", params, verifier.timeout)
|
||||
return &Caller_Get_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type Caller_Get_OngoingVerification struct {
|
||||
mock *MockCaller
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *Caller_Get_OngoingVerification) GetCapturedArguments() (string, string) {
|
||||
_param0, _param1 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1], _param1[len(_param1)-1]
|
||||
}
|
||||
|
||||
func (c *Caller_Get_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
_param1 = make([]string, len(params[1]))
|
||||
for u, param := range params[1] {
|
||||
_param1[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierCaller) List(_param0 string) *Caller_List_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "List", params, verifier.timeout)
|
||||
return &Caller_List_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type Caller_List_OngoingVerification struct {
|
||||
mock *MockCaller
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *Caller_List_OngoingVerification) GetCapturedArguments() string {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *Caller_List_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
// Code generated by pegomock. DO NOT EDIT.
|
||||
// Source: github.com/k8sland/k9s/resource (interfaces: ClusterIfc)
|
||||
|
||||
package resource_test
|
||||
|
||||
import (
|
||||
pegomock "github.com/petergtz/pegomock"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MockClusterIfc struct {
|
||||
fail func(message string, callerSkip ...int)
|
||||
}
|
||||
|
||||
func NewMockClusterIfc() *MockClusterIfc {
|
||||
return &MockClusterIfc{fail: pegomock.GlobalFailHandler}
|
||||
}
|
||||
|
||||
func (mock *MockClusterIfc) ClusterName() string {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockClusterIfc().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("ClusterName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem()})
|
||||
var ret0 string
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockClusterIfc) Version() (string, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockClusterIfc().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Version", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 string
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockClusterIfc) VerifyWasCalledOnce() *VerifierClusterIfc {
|
||||
return &VerifierClusterIfc{
|
||||
mock: mock,
|
||||
invocationCountMatcher: pegomock.Times(1),
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockClusterIfc) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierClusterIfc {
|
||||
return &VerifierClusterIfc{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockClusterIfc) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierClusterIfc {
|
||||
return &VerifierClusterIfc{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
inOrderContext: inOrderContext,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockClusterIfc) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierClusterIfc {
|
||||
return &VerifierClusterIfc{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type VerifierClusterIfc struct {
|
||||
mock *MockClusterIfc
|
||||
invocationCountMatcher pegomock.Matcher
|
||||
inOrderContext *pegomock.InOrderContext
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierClusterIfc) ClusterName() *ClusterIfc_ClusterName_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ClusterName", params, verifier.timeout)
|
||||
return &ClusterIfc_ClusterName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type ClusterIfc_ClusterName_OngoingVerification struct {
|
||||
mock *MockClusterIfc
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *ClusterIfc_ClusterName_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *ClusterIfc_ClusterName_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierClusterIfc) Version() *ClusterIfc_Version_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Version", params, verifier.timeout)
|
||||
return &ClusterIfc_Version_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type ClusterIfc_Version_OngoingVerification struct {
|
||||
mock *MockClusterIfc
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *ClusterIfc_Version_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *ClusterIfc_Version_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
// Code generated by pegomock. DO NOT EDIT.
|
||||
// Source: github.com/k8sland/k9s/resource (interfaces: MetricsIfc)
|
||||
|
||||
package resource_test
|
||||
|
||||
import (
|
||||
k8s "github.com/k8sland/k9s/resource/k8s"
|
||||
pegomock "github.com/petergtz/pegomock"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MockMetricsIfc struct {
|
||||
fail func(message string, callerSkip ...int)
|
||||
}
|
||||
|
||||
func NewMockMetricsIfc() *MockMetricsIfc {
|
||||
return &MockMetricsIfc{fail: pegomock.GlobalFailHandler}
|
||||
}
|
||||
|
||||
func (mock *MockMetricsIfc) NodeMetrics() (k8s.Metric, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockMetricsIfc().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("NodeMetrics", params, []reflect.Type{reflect.TypeOf((*k8s.Metric)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 k8s.Metric
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(k8s.Metric)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockMetricsIfc) PerNodeMetrics(_param0 []v1.Node) (map[string]k8s.Metric, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockMetricsIfc().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("PerNodeMetrics", params, []reflect.Type{reflect.TypeOf((*map[string]k8s.Metric)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 map[string]k8s.Metric
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(map[string]k8s.Metric)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockMetricsIfc) PodMetrics() (map[string]k8s.Metric, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockMetricsIfc().")
|
||||
}
|
||||
params := []pegomock.Param{}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("PodMetrics", params, []reflect.Type{reflect.TypeOf((*map[string]k8s.Metric)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 map[string]k8s.Metric
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(map[string]k8s.Metric)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockMetricsIfc) VerifyWasCalledOnce() *VerifierMetricsIfc {
|
||||
return &VerifierMetricsIfc{
|
||||
mock: mock,
|
||||
invocationCountMatcher: pegomock.Times(1),
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockMetricsIfc) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMetricsIfc {
|
||||
return &VerifierMetricsIfc{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockMetricsIfc) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMetricsIfc {
|
||||
return &VerifierMetricsIfc{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
inOrderContext: inOrderContext,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockMetricsIfc) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMetricsIfc {
|
||||
return &VerifierMetricsIfc{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type VerifierMetricsIfc struct {
|
||||
mock *MockMetricsIfc
|
||||
invocationCountMatcher pegomock.Matcher
|
||||
inOrderContext *pegomock.InOrderContext
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierMetricsIfc) NodeMetrics() *MetricsIfc_NodeMetrics_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NodeMetrics", params, verifier.timeout)
|
||||
return &MetricsIfc_NodeMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MetricsIfc_NodeMetrics_OngoingVerification struct {
|
||||
mock *MockMetricsIfc
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MetricsIfc_NodeMetrics_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MetricsIfc_NodeMetrics_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
||||
func (verifier *VerifierMetricsIfc) PerNodeMetrics(_param0 []v1.Node) *MetricsIfc_PerNodeMetrics_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "PerNodeMetrics", params, verifier.timeout)
|
||||
return &MetricsIfc_PerNodeMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MetricsIfc_PerNodeMetrics_OngoingVerification struct {
|
||||
mock *MockMetricsIfc
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MetricsIfc_PerNodeMetrics_OngoingVerification) GetCapturedArguments() []v1.Node {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *MetricsIfc_PerNodeMetrics_OngoingVerification) GetAllCapturedArguments() (_param0 [][]v1.Node) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([][]v1.Node, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.([]v1.Node)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierMetricsIfc) PodMetrics() *MetricsIfc_PodMetrics_OngoingVerification {
|
||||
params := []pegomock.Param{}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "PodMetrics", params, verifier.timeout)
|
||||
return &MetricsIfc_PodMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type MetricsIfc_PodMetrics_OngoingVerification struct {
|
||||
mock *MockMetricsIfc
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *MetricsIfc_PodMetrics_OngoingVerification) GetCapturedArguments() {
|
||||
}
|
||||
|
||||
func (c *MetricsIfc_PodMetrics_OngoingVerification) GetAllCapturedArguments() {
|
||||
}
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
// Code generated by pegomock. DO NOT EDIT.
|
||||
// Source: github.com/k8sland/k9s/resource (interfaces: Resource)
|
||||
|
||||
package resource_test
|
||||
|
||||
import (
|
||||
resource "github.com/k8sland/k9s/resource"
|
||||
pegomock "github.com/petergtz/pegomock"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MockResource struct {
|
||||
fail func(message string, callerSkip ...int)
|
||||
}
|
||||
|
||||
func NewMockResource() *MockResource {
|
||||
return &MockResource{fail: pegomock.GlobalFailHandler}
|
||||
}
|
||||
|
||||
func (mock *MockResource) Delete(_param0 string) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockResource().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Delete", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(error)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockResource) Get(_param0 string) (resource.Columnar, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockResource().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Get", params, []reflect.Type{reflect.TypeOf((*resource.Columnar)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 resource.Columnar
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(resource.Columnar)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockResource) Header(_param0 string) resource.Row {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockResource().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Header", params, []reflect.Type{reflect.TypeOf((*resource.Row)(nil)).Elem()})
|
||||
var ret0 resource.Row
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(resource.Row)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockResource) List(_param0 string) (resource.Columnars, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockResource().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("List", params, []reflect.Type{reflect.TypeOf((*resource.Columnars)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 resource.Columnars
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(resource.Columnars)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockResource) Marshal(_param0 string) (string, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockResource().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Marshal", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 string
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockResource) VerifyWasCalledOnce() *VerifierResource {
|
||||
return &VerifierResource{
|
||||
mock: mock,
|
||||
invocationCountMatcher: pegomock.Times(1),
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockResource) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierResource {
|
||||
return &VerifierResource{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockResource) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierResource {
|
||||
return &VerifierResource{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
inOrderContext: inOrderContext,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockResource) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierResource {
|
||||
return &VerifierResource{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type VerifierResource struct {
|
||||
mock *MockResource
|
||||
invocationCountMatcher pegomock.Matcher
|
||||
inOrderContext *pegomock.InOrderContext
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierResource) Delete(_param0 string) *Resource_Delete_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Delete", params, verifier.timeout)
|
||||
return &Resource_Delete_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type Resource_Delete_OngoingVerification struct {
|
||||
mock *MockResource
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *Resource_Delete_OngoingVerification) GetCapturedArguments() string {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *Resource_Delete_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierResource) Get(_param0 string) *Resource_Get_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Get", params, verifier.timeout)
|
||||
return &Resource_Get_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type Resource_Get_OngoingVerification struct {
|
||||
mock *MockResource
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *Resource_Get_OngoingVerification) GetCapturedArguments() string {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *Resource_Get_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierResource) Header(_param0 string) *Resource_Header_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Header", params, verifier.timeout)
|
||||
return &Resource_Header_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type Resource_Header_OngoingVerification struct {
|
||||
mock *MockResource
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *Resource_Header_OngoingVerification) GetCapturedArguments() string {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *Resource_Header_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierResource) List(_param0 string) *Resource_List_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "List", params, verifier.timeout)
|
||||
return &Resource_List_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type Resource_List_OngoingVerification struct {
|
||||
mock *MockResource
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *Resource_List_OngoingVerification) GetCapturedArguments() string {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *Resource_List_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierResource) Marshal(_param0 string) *Resource_Marshal_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Marshal", params, verifier.timeout)
|
||||
return &Resource_Marshal_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type Resource_Marshal_OngoingVerification struct {
|
||||
mock *MockResource
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *Resource_Marshal_OngoingVerification) GetCapturedArguments() string {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *Resource_Marshal_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
// Code generated by pegomock. DO NOT EDIT.
|
||||
// Source: github.com/k8sland/k9s/resource (interfaces: SwitchableRes)
|
||||
|
||||
package resource_test
|
||||
|
||||
import (
|
||||
k8s "github.com/k8sland/k9s/resource/k8s"
|
||||
pegomock "github.com/petergtz/pegomock"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MockSwitchableRes struct {
|
||||
fail func(message string, callerSkip ...int)
|
||||
}
|
||||
|
||||
func NewMockSwitchableRes() *MockSwitchableRes {
|
||||
return &MockSwitchableRes{fail: pegomock.GlobalFailHandler}
|
||||
}
|
||||
|
||||
func (mock *MockSwitchableRes) Delete(_param0 string, _param1 string) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockSwitchableRes().")
|
||||
}
|
||||
params := []pegomock.Param{_param0, _param1}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Delete", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(error)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockSwitchableRes) Get(_param0 string, _param1 string) (interface{}, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockSwitchableRes().")
|
||||
}
|
||||
params := []pegomock.Param{_param0, _param1}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Get", params, []reflect.Type{reflect.TypeOf((*interface{})(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 interface{}
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(interface{})
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockSwitchableRes) List(_param0 string) (k8s.Collection, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockSwitchableRes().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("List", params, []reflect.Type{reflect.TypeOf((*k8s.Collection)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 k8s.Collection
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(k8s.Collection)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockSwitchableRes) Switch(_param0 string) error {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockSwitchableRes().")
|
||||
}
|
||||
params := []pegomock.Param{_param0}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("Switch", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(error)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
}
|
||||
|
||||
func (mock *MockSwitchableRes) VerifyWasCalledOnce() *VerifierSwitchableRes {
|
||||
return &VerifierSwitchableRes{
|
||||
mock: mock,
|
||||
invocationCountMatcher: pegomock.Times(1),
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockSwitchableRes) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierSwitchableRes {
|
||||
return &VerifierSwitchableRes{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockSwitchableRes) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierSwitchableRes {
|
||||
return &VerifierSwitchableRes{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
inOrderContext: inOrderContext,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *MockSwitchableRes) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierSwitchableRes {
|
||||
return &VerifierSwitchableRes{
|
||||
mock: mock,
|
||||
invocationCountMatcher: invocationCountMatcher,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type VerifierSwitchableRes struct {
|
||||
mock *MockSwitchableRes
|
||||
invocationCountMatcher pegomock.Matcher
|
||||
inOrderContext *pegomock.InOrderContext
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (verifier *VerifierSwitchableRes) Delete(_param0 string, _param1 string) *SwitchableRes_Delete_OngoingVerification {
|
||||
params := []pegomock.Param{_param0, _param1}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Delete", params, verifier.timeout)
|
||||
return &SwitchableRes_Delete_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type SwitchableRes_Delete_OngoingVerification struct {
|
||||
mock *MockSwitchableRes
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *SwitchableRes_Delete_OngoingVerification) GetCapturedArguments() (string, string) {
|
||||
_param0, _param1 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1], _param1[len(_param1)-1]
|
||||
}
|
||||
|
||||
func (c *SwitchableRes_Delete_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
_param1 = make([]string, len(params[1]))
|
||||
for u, param := range params[1] {
|
||||
_param1[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierSwitchableRes) Get(_param0 string, _param1 string) *SwitchableRes_Get_OngoingVerification {
|
||||
params := []pegomock.Param{_param0, _param1}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Get", params, verifier.timeout)
|
||||
return &SwitchableRes_Get_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type SwitchableRes_Get_OngoingVerification struct {
|
||||
mock *MockSwitchableRes
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *SwitchableRes_Get_OngoingVerification) GetCapturedArguments() (string, string) {
|
||||
_param0, _param1 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1], _param1[len(_param1)-1]
|
||||
}
|
||||
|
||||
func (c *SwitchableRes_Get_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
_param1 = make([]string, len(params[1]))
|
||||
for u, param := range params[1] {
|
||||
_param1[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierSwitchableRes) List(_param0 string) *SwitchableRes_List_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "List", params, verifier.timeout)
|
||||
return &SwitchableRes_List_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type SwitchableRes_List_OngoingVerification struct {
|
||||
mock *MockSwitchableRes
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *SwitchableRes_List_OngoingVerification) GetCapturedArguments() string {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *SwitchableRes_List_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (verifier *VerifierSwitchableRes) Switch(_param0 string) *SwitchableRes_Switch_OngoingVerification {
|
||||
params := []pegomock.Param{_param0}
|
||||
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Switch", params, verifier.timeout)
|
||||
return &SwitchableRes_Switch_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
|
||||
}
|
||||
|
||||
type SwitchableRes_Switch_OngoingVerification struct {
|
||||
mock *MockSwitchableRes
|
||||
methodInvocations []pegomock.MethodInvocation
|
||||
}
|
||||
|
||||
func (c *SwitchableRes_Switch_OngoingVerification) GetCapturedArguments() string {
|
||||
_param0 := c.GetAllCapturedArguments()
|
||||
return _param0[len(_param0)-1]
|
||||
}
|
||||
|
||||
func (c *SwitchableRes_Switch_OngoingVerification) GetAllCapturedArguments() (_param0 []string) {
|
||||
params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
|
||||
if len(params) > 0 {
|
||||
_param0 = make([]string, len(params[0]))
|
||||
for u, param := range params[0] {
|
||||
_param0[u] = param.(string)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
labelNodeRolePrefix = "node-role.kubernetes.io/"
|
||||
nodeLabelRole = "kubernetes.io/role"
|
||||
)
|
||||
|
||||
// Node tracks a kubernetes resource.
|
||||
type Node struct {
|
||||
*Base
|
||||
instance *v1.Node
|
||||
metricSvc MetricsIfc
|
||||
metrics k8s.Metric
|
||||
}
|
||||
|
||||
// NewNodeList returns a new resource list.
|
||||
func NewNodeList(ns string) List {
|
||||
return NewNodeListWithArgs(ns, NewNode())
|
||||
}
|
||||
|
||||
// NewNodeListWithArgs returns a new resource list.
|
||||
func NewNodeListWithArgs(ns string, res Resource) List {
|
||||
return newList(NotNamespaced, "no", res, ViewAccess)
|
||||
}
|
||||
|
||||
// NewNode instantiates a new Endpoint.
|
||||
func NewNode() *Node {
|
||||
return NewNodeWithArgs(k8s.NewNode(), k8s.NewMetricsServer())
|
||||
}
|
||||
|
||||
// NewNodeWithArgs instantiates a new Endpoint.
|
||||
func NewNodeWithArgs(r k8s.Res, mx MetricsIfc) *Node {
|
||||
ep := &Node{
|
||||
metricSvc: mx,
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ep.creator = ep
|
||||
return ep
|
||||
}
|
||||
|
||||
// NewInstance builds a new Endpoint instance from a k8s resource.
|
||||
func (*Node) NewInstance(i interface{}) Columnar {
|
||||
cm := NewNode()
|
||||
switch i.(type) {
|
||||
case *v1.Node:
|
||||
cm.instance = i.(*v1.Node)
|
||||
case v1.Node:
|
||||
ii := i.(v1.Node)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// List all resources for a given namespace.
|
||||
func (r *Node) List(ns string) (Columnars, error) {
|
||||
ii, err := r.caller.List(AllNamespaces)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nn := make([]v1.Node, len(ii))
|
||||
for k, i := range ii {
|
||||
nn[k] = i.(v1.Node)
|
||||
}
|
||||
|
||||
cc := make(Columnars, 0, len(nn))
|
||||
mx, err := r.metricSvc.PerNodeMetrics(nn)
|
||||
if err != nil {
|
||||
return cc, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(nn); i++ {
|
||||
n := r.NewInstance(&nn[i]).(*Node)
|
||||
n.metrics = mx[nn[i].Name]
|
||||
cc = append(cc, n)
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Marshal a resource to yaml.
|
||||
func (r *Node) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
no := i.(*v1.Node)
|
||||
no.TypeMeta.APIVersion = "v1"
|
||||
no.TypeMeta.Kind = "Node"
|
||||
raw, err := yaml.Marshal(no)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header returns resource header.
|
||||
func (*Node) Header(ns string) Row {
|
||||
return Row{
|
||||
"NAME",
|
||||
"STATUS",
|
||||
"ROLES",
|
||||
"VERSION",
|
||||
"INTERNAL-IP",
|
||||
"EXTERNAL-IP",
|
||||
"CPU",
|
||||
"MEM",
|
||||
"AVAILABLE_CPU",
|
||||
"AVAILABLE_MEM",
|
||||
"AGE",
|
||||
}
|
||||
}
|
||||
|
||||
// Fields returns displayable fields.
|
||||
func (r *Node) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
|
||||
status := r.status(i)
|
||||
iIP, eIP := r.getIPs(i.Status.Addresses)
|
||||
iIP, eIP = missing(iIP), missing(eIP)
|
||||
|
||||
roles := missing(strings.Join(findNodeRoles(i), ","))
|
||||
cpu, mem, acpu, amem := na(r.metrics.CPU), na(r.metrics.Mem), na(r.metrics.AvailCPU), na(r.metrics.AvailMem)
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
status,
|
||||
roles,
|
||||
i.Status.NodeInfo.KernelVersion,
|
||||
iIP,
|
||||
eIP,
|
||||
cpu,
|
||||
mem,
|
||||
acpu,
|
||||
amem,
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*Node) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func (*Node) getIPs(addrs []v1.NodeAddress) (iIP, eIP string) {
|
||||
for _, a := range addrs {
|
||||
switch a.Type {
|
||||
case v1.NodeExternalIP:
|
||||
eIP = a.Address
|
||||
case v1.NodeInternalIP:
|
||||
iIP = a.Address
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Node) status(i *v1.Node) string {
|
||||
conditionMap := make(map[v1.NodeConditionType]*v1.NodeCondition)
|
||||
NodeAllConditions := []v1.NodeConditionType{v1.NodeReady}
|
||||
for n := range i.Status.Conditions {
|
||||
cond := i.Status.Conditions[n]
|
||||
conditionMap[cond.Type] = &cond
|
||||
}
|
||||
var status []string
|
||||
for _, validCondition := range NodeAllConditions {
|
||||
if condition, ok := conditionMap[validCondition]; ok {
|
||||
if condition.Status == v1.ConditionTrue {
|
||||
status = append(status, string(condition.Type))
|
||||
} else {
|
||||
status = append(status, "Not"+string(condition.Type))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(status) == 0 {
|
||||
status = append(status, "Unknown")
|
||||
}
|
||||
if i.Spec.Unschedulable {
|
||||
status = append(status, "SchedulingDisabled")
|
||||
}
|
||||
return strings.Join(status, ",")
|
||||
}
|
||||
|
||||
func findNodeRoles(i *v1.Node) []string {
|
||||
roles := sets.NewString()
|
||||
for k, v := range i.Labels {
|
||||
switch {
|
||||
case strings.HasPrefix(k, labelNodeRolePrefix):
|
||||
if role := strings.TrimPrefix(k, labelNodeRolePrefix); len(role) > 0 {
|
||||
roles.Insert(role)
|
||||
}
|
||||
case k == nodeLabelRole && v != "":
|
||||
roles.Insert(v)
|
||||
}
|
||||
}
|
||||
return roles.List()
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestNodeListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewNodeList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.Equal(t, "no", l.GetName())
|
||||
for _, a := range []int{resource.ViewAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeFields(t *testing.T) {
|
||||
r := newNode().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestNodeMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
mx := NewMockMetricsIfc()
|
||||
m.When(mx.PerNodeMetrics([]v1.Node{*k8sNode()})).
|
||||
ThenReturn(map[string]k8s.Metric{"fred": k8s.Metric{}}, nil)
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sNode(), nil)
|
||||
|
||||
cm := resource.NewNodeWithArgs(ca, mx)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, noYaml(), ma)
|
||||
}
|
||||
|
||||
func TestNodeListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
mx := NewMockMetricsIfc()
|
||||
m.When(mx.PerNodeMetrics([]v1.Node{*k8sNode()})).
|
||||
ThenReturn(map[string]k8s.Metric{"fred": k8s.Metric{}}, nil)
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List("")).ThenReturn(k8s.Collection{*k8sNode()}, nil)
|
||||
|
||||
l := resource.NewNodeListWithArgs("", resource.NewNodeWithArgs(ca, mx))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List("")
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["fred"]
|
||||
assert.Equal(t, 11, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestNodeListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
mx := NewMockMetricsIfc()
|
||||
m.When(mx.PerNodeMetrics([]v1.Node{*k8sNode()})).
|
||||
ThenReturn(map[string]k8s.Metric{"fred": k8s.Metric{}}, nil)
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sNode(), nil)
|
||||
l := resource.NewNodeListWithArgs("blee", resource.NewNodeWithArgs(ca, mx))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sNode() *v1.Node {
|
||||
return &v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Spec: v1.NodeSpec{},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Address: "1.1.1.1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newNode() resource.Columnar {
|
||||
return resource.NewNode().NewInstance(k8sNode())
|
||||
}
|
||||
|
||||
func noYaml() string {
|
||||
return `typemeta:
|
||||
kind: Node
|
||||
apiversion: v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: ""
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
podcidr: ""
|
||||
providerid: ""
|
||||
unschedulable: false
|
||||
taints: []
|
||||
configsource: null
|
||||
donotuse_externalid: ""
|
||||
status:
|
||||
capacity: {}
|
||||
allocatable: {}
|
||||
phase: ""
|
||||
conditions: []
|
||||
addresses:
|
||||
- type: ""
|
||||
address: 1.1.1.1
|
||||
daemonendpoints:
|
||||
kubeletendpoint:
|
||||
port: 0
|
||||
nodeinfo:
|
||||
machineid: ""
|
||||
systemuuid: ""
|
||||
bootid: ""
|
||||
kernelversion: ""
|
||||
osimage: ""
|
||||
containerruntimeversion: ""
|
||||
kubeletversion: ""
|
||||
kubeproxyversion: ""
|
||||
operatingsystem: ""
|
||||
architecture: ""
|
||||
images: []
|
||||
volumesinuse: []
|
||||
volumesattached: []
|
||||
config: null
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Namespace tracks a kubernetes resource.
|
||||
type Namespace struct {
|
||||
*Base
|
||||
instance *v1.Namespace
|
||||
}
|
||||
|
||||
// NewNamespaceList returns a new resource list.
|
||||
func NewNamespaceList(ns string) List {
|
||||
return NewNamespaceListWithArgs(ns, NewNamespace())
|
||||
}
|
||||
|
||||
// NewNamespaceListWithArgs returns a new resource list.
|
||||
func NewNamespaceListWithArgs(ns string, res Resource) List {
|
||||
return newList(NotNamespaced, "ns", res, CRUDAccess)
|
||||
}
|
||||
|
||||
// NewNamespace instantiates a new Endpoint.
|
||||
func NewNamespace() *Namespace {
|
||||
return NewNamespaceWithArgs(k8s.NewNamespace())
|
||||
}
|
||||
|
||||
// NewNamespaceWithArgs instantiates a new Endpoint.
|
||||
func NewNamespaceWithArgs(r k8s.Res) *Namespace {
|
||||
ep := &Namespace{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ep.creator = ep
|
||||
return ep
|
||||
}
|
||||
|
||||
// NewInstance builds a new Endpoint instance from a k8s resource.
|
||||
func (*Namespace) NewInstance(i interface{}) Columnar {
|
||||
cm := NewNamespace()
|
||||
switch i.(type) {
|
||||
case *v1.Namespace:
|
||||
cm.instance = i.(*v1.Namespace)
|
||||
case v1.Namespace:
|
||||
ii := i.(v1.Namespace)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal a resource to yaml.
|
||||
func (r *Namespace) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
nss := i.(*v1.Namespace)
|
||||
nss.TypeMeta.APIVersion = "v1"
|
||||
nss.TypeMeta.Kind = "Namespace"
|
||||
raw, err := yaml.Marshal(nss)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header returns resource header.
|
||||
func (*Namespace) Header(ns string) Row {
|
||||
return Row{"NAME", "STATUS", "AGE"}
|
||||
}
|
||||
|
||||
// Fields returns displayable fields.
|
||||
func (r *Namespace) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
return append(ff,
|
||||
i.Name,
|
||||
string(i.Status.Phase),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*Namespace) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestNamespaceListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewNamespaceList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.Equal(t, "ns", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "STATUS", "AGE"}, newNamespace().Header("default"))
|
||||
}
|
||||
|
||||
func TestNamespaceFields(t *testing.T) {
|
||||
r := newNamespace().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestNamespaceMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("", "fred")).ThenReturn(k8sNamespace(), nil)
|
||||
|
||||
cm := resource.NewNamespaceWithArgs(ca)
|
||||
ma, err := cm.Marshal("fred")
|
||||
ca.VerifyWasCalledOnce().Get("", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, nsYaml(), ma)
|
||||
}
|
||||
|
||||
func TestNamespaceListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sNamespace()}, nil)
|
||||
|
||||
l := resource.NewNamespaceListWithArgs("-", resource.NewNamespaceWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 3, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestNamespaceListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sNamespace(), nil)
|
||||
l := resource.NewNamespaceListWithArgs("blee", resource.NewNamespaceWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sNamespace() *v1.Namespace {
|
||||
return &v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newNamespace() resource.Columnar {
|
||||
return resource.NewNamespace().NewInstance(k8sNamespace())
|
||||
}
|
||||
|
||||
func nsYaml() string {
|
||||
return `typemeta:
|
||||
kind: Namespace
|
||||
apiversion: v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
finalizers: []
|
||||
status:
|
||||
phase: ""
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,382 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const defaultTimeout = 3
|
||||
|
||||
type (
|
||||
// Container represents a resource that encompass multiple containers.
|
||||
Container interface {
|
||||
Containers(path string) ([]string, error)
|
||||
}
|
||||
|
||||
// Tailable represents a resource with tailable logs.
|
||||
Tailable interface {
|
||||
Logs(c chan<- string, ns, na, co string) (context.CancelFunc, error)
|
||||
}
|
||||
|
||||
// TailableResource is a resource that have tailable logs.
|
||||
TailableResource interface {
|
||||
Resource
|
||||
Tailable
|
||||
}
|
||||
|
||||
// Pod that can be displayed in a table and interacted with.
|
||||
Pod struct {
|
||||
*Base
|
||||
instance *v1.Pod
|
||||
metricSvc MetricsIfc
|
||||
metrics k8s.Metric
|
||||
}
|
||||
)
|
||||
|
||||
// NewPodList returns a new resource list.
|
||||
func NewPodList(ns string) List {
|
||||
return NewPodListWithArgs(ns, NewPod())
|
||||
}
|
||||
|
||||
// NewPodListWithArgs returns a new resource list.
|
||||
func NewPodListWithArgs(ns string, res Resource) List {
|
||||
l := newList(ns, "po", res, AllVerbsAccess)
|
||||
l.xray = true
|
||||
return l
|
||||
}
|
||||
|
||||
// NewPod returns a new Pod instance.
|
||||
func NewPod() *Pod {
|
||||
return NewPodWithArgs(k8s.NewPod(), k8s.NewMetricsServer())
|
||||
}
|
||||
|
||||
// NewPodWithArgs returns a new Pod instance.
|
||||
func NewPodWithArgs(r k8s.Res, mx MetricsIfc) *Pod {
|
||||
p := &Pod{
|
||||
metricSvc: mx,
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
p.creator = p
|
||||
return p
|
||||
}
|
||||
|
||||
// NewInstance builds a new Pod instance from a k8s resource.
|
||||
func (r *Pod) NewInstance(i interface{}) Columnar {
|
||||
pod := NewPod()
|
||||
switch i.(type) {
|
||||
case *v1.Pod:
|
||||
pod.instance = i.(*v1.Pod)
|
||||
case v1.Pod:
|
||||
ii := i.(v1.Pod)
|
||||
pod.instance = &ii
|
||||
case *interface{}:
|
||||
ptr := *i.(*interface{})
|
||||
po := ptr.(v1.Pod)
|
||||
pod.instance = &po
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
pod.path = r.namespacedName(pod.instance.ObjectMeta)
|
||||
return pod
|
||||
}
|
||||
|
||||
// Metrics retrieves cpu/mem resource consumption on a pod.
|
||||
func (r *Pod) Metrics() k8s.Metric {
|
||||
return r.metrics
|
||||
}
|
||||
|
||||
// SetMetrics set the current k8s resource metrics on a given pod.
|
||||
func (r *Pod) SetMetrics(m k8s.Metric) {
|
||||
r.metrics = m
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *Pod) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
po := i.(*v1.Pod)
|
||||
po.TypeMeta.APIVersion = "v1"
|
||||
po.TypeMeta.Kind = "Pod"
|
||||
raw, err := yaml.Marshal(po)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Containers lists out all the docker contrainers name contained in a pod.
|
||||
func (r *Pod) Containers(path string) ([]string, error) {
|
||||
ns, po := namespaced(path)
|
||||
return r.caller.(k8s.PodRes).Containers(ns, po)
|
||||
}
|
||||
|
||||
// Logs tails a given container logs
|
||||
func (r *Pod) Logs(c chan<- string, ns, n, co string) (context.CancelFunc, error) {
|
||||
req := r.caller.(k8s.PodRes).Logs(ns, n, co)
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
req.Context(ctx)
|
||||
|
||||
blocked := true
|
||||
go func() {
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
if blocked {
|
||||
close(c)
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}()
|
||||
// This call will block if nothing is in the stream!!
|
||||
stream, err := req.Stream()
|
||||
blocked = false
|
||||
if err != nil {
|
||||
return cancel, fmt.Errorf("Log tail request failed for pod `%s/%s:%s", ns, n, co)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
stream.Close()
|
||||
cancel()
|
||||
close(c)
|
||||
}()
|
||||
|
||||
scanner := bufio.NewScanner(stream)
|
||||
for scanner.Scan() {
|
||||
c <- scanner.Text()
|
||||
}
|
||||
}()
|
||||
return cancel, nil
|
||||
}
|
||||
|
||||
// List resources for a given namespace.
|
||||
func (r *Pod) List(ns string) (Columnars, error) {
|
||||
ii, err := r.caller.List(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metrics, err := r.metricSvc.PodMetrics()
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
|
||||
cc := make(Columnars, 0, len(ii))
|
||||
for i := 0; i < len(ii); i++ {
|
||||
po := r.NewInstance(&ii[i]).(MxColumnar)
|
||||
po.SetMetrics(metrics[po.Name()])
|
||||
cc = append(cc, po)
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*Pod) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh,
|
||||
"NAME",
|
||||
"READY",
|
||||
"STATUS",
|
||||
"RESTARTS",
|
||||
"CPU",
|
||||
"MEM",
|
||||
"IP",
|
||||
"NODE",
|
||||
"QOS",
|
||||
"AGE",
|
||||
)
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *Pod) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
cr, _, rc, cc := r.statuses()
|
||||
return append(ff,
|
||||
i.ObjectMeta.Name,
|
||||
strconv.Itoa(cr)+"/"+strconv.Itoa(len(cc)),
|
||||
r.phase(i.Status),
|
||||
strconv.Itoa(rc),
|
||||
r.metrics.CPU,
|
||||
r.metrics.Mem,
|
||||
i.Status.PodIP,
|
||||
i.Status.HostIP,
|
||||
string(i.Status.QOSClass),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extra info about the resource.
|
||||
func (r *Pod) ExtFields() Properties {
|
||||
i := r.instance
|
||||
|
||||
// po := k8s.Pod{}
|
||||
// e, err := po.Events(i.Namespace, i.Name)
|
||||
// if err != nil {
|
||||
// log.Error("Boom!", err)
|
||||
// }
|
||||
// if len(e.Items) > 0 {
|
||||
// log.Println("Events", ee.Items)
|
||||
// }
|
||||
|
||||
return Properties{
|
||||
"Priority": strconv.Itoa(int(*i.Spec.Priority)),
|
||||
"Priority Class": missing(i.Spec.PriorityClassName),
|
||||
"Labels": mapToStr(i.Labels),
|
||||
"Annotations": mapToStr(i.ObjectMeta.Annotations),
|
||||
"Containers": r.toContainers(i.Spec.Containers),
|
||||
"Init Containers": r.toContainers(i.Spec.InitContainers),
|
||||
"Node Selectors": mapToStr(i.Spec.NodeSelector),
|
||||
"Volumes": r.toVolumes(i.Spec.Volumes),
|
||||
// "Events": r.toEvents(e),
|
||||
}
|
||||
}
|
||||
|
||||
// func (r *Pod) toEvents(e *v1.EventList) []string {
|
||||
// ss := make([]string, 0, len(e.Items)+1)
|
||||
// for _, h := range([]string{"Type", "Reason", "From", "Message", "Age"}) {
|
||||
// ss[0] = fmt.Printf("%10s %10s %20s %30s", )
|
||||
// }
|
||||
// for i, e := range e.Items {
|
||||
// ss[i] = e.
|
||||
|
||||
// }
|
||||
// return ss
|
||||
// }
|
||||
|
||||
func (r *Pod) toVolumes(vv []v1.Volume) map[string]interface{} {
|
||||
m := make(map[string]interface{}, len(vv))
|
||||
for _, v := range vv {
|
||||
m[v.Name] = r.toVolume(v)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *Pod) toVolume(v v1.Volume) map[string]interface{} {
|
||||
switch {
|
||||
case v.Secret != nil:
|
||||
return map[string]interface{}{
|
||||
"Type": "Secret",
|
||||
"Name": v.Secret.SecretName,
|
||||
"Optional": r.boolPtrToStr(v.Secret.Optional),
|
||||
}
|
||||
case v.AWSElasticBlockStore != nil:
|
||||
return map[string]interface{}{
|
||||
"Type": v.AWSElasticBlockStore.FSType,
|
||||
"VolumeID": v.AWSElasticBlockStore.VolumeID,
|
||||
"Partition": strconv.Itoa(int(v.AWSElasticBlockStore.Partition)),
|
||||
"ReadOnly": boolToStr(v.AWSElasticBlockStore.ReadOnly),
|
||||
}
|
||||
default:
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Pod) toContainers(cc []v1.Container) map[string]interface{} {
|
||||
m := make(map[string]interface{}, len(cc))
|
||||
for _, c := range cc {
|
||||
m[c.Name] = map[string]interface{}{
|
||||
"Image": c.Image,
|
||||
"Environment": r.toEnv(c.Env),
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *Pod) toEnv(ee []v1.EnvVar) []string {
|
||||
if len(ee) == 0 {
|
||||
return []string{MissingValue}
|
||||
}
|
||||
|
||||
ss := make([]string, len(ee))
|
||||
for i, e := range ee {
|
||||
s := r.toEnvFrom(e.ValueFrom)
|
||||
if len(s) == 0 {
|
||||
ss[i] = e.Name + "=" + e.Value
|
||||
} else {
|
||||
ss[i] = e.Name + "=" + e.Value + "(" + s + ")"
|
||||
}
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
func (r *Pod) toEnvFrom(e *v1.EnvVarSource) string {
|
||||
if e == nil {
|
||||
return MissingValue
|
||||
}
|
||||
|
||||
var s string
|
||||
switch {
|
||||
case e.ConfigMapKeyRef != nil:
|
||||
f := e.ConfigMapKeyRef
|
||||
s += f.Name + ":" + f.Key + "(" + r.boolPtrToStr(f.Optional) + ")"
|
||||
case e.FieldRef != nil:
|
||||
f := e.FieldRef
|
||||
s += f.FieldPath + ":" + f.APIVersion
|
||||
case e.SecretKeyRef != nil:
|
||||
f := e.SecretKeyRef
|
||||
s += f.Name + ":" + f.Key + "(" + r.boolPtrToStr(f.Optional) + ")"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *Pod) boolPtrToStr(b *bool) string {
|
||||
if b == nil {
|
||||
return "false"
|
||||
}
|
||||
|
||||
return boolToStr(*b)
|
||||
}
|
||||
|
||||
func (r *Pod) statuses() (cr, ct, rc int, cc []v1.ContainerStatus) {
|
||||
cc = r.instance.Status.ContainerStatuses
|
||||
for _, c := range cc {
|
||||
if c.State.Terminated != nil {
|
||||
ct++
|
||||
}
|
||||
if c.Ready {
|
||||
cr = cr + 1
|
||||
}
|
||||
rc += int(c.RestartCount)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (*Pod) phase(s v1.PodStatus) string {
|
||||
status := "Pending"
|
||||
for _, cs := range s.ContainerStatuses {
|
||||
switch {
|
||||
case cs.State.Running != nil:
|
||||
status = "Running"
|
||||
case cs.State.Waiting != nil:
|
||||
status = cs.State.Waiting.Reason
|
||||
case cs.State.Terminated != nil:
|
||||
status = "Terminating"
|
||||
if len(cs.State.Terminated.Reason) != 0 {
|
||||
status = cs.State.Terminated.Reason
|
||||
}
|
||||
}
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestPodListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewPodList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.Equal(t, "po", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "READY", "STATUS", "RESTARTS", "CPU", "MEM", "IP", "NODE", "QOS", "AGE"}, newPod().Header("default"))
|
||||
}
|
||||
|
||||
func TestPodFields(t *testing.T) {
|
||||
r := newPod().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestPodMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
mx := NewMockMetricsIfc()
|
||||
m.When(mx.PodMetrics()).ThenReturn(map[string]k8s.Metric{"fred": k8s.Metric{}}, nil)
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sPod(), nil)
|
||||
|
||||
cm := resource.NewPodWithArgs(ca, mx)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, poYaml(), ma)
|
||||
}
|
||||
|
||||
func TestPodListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
mx := NewMockMetricsIfc()
|
||||
m.When(mx.PodMetrics()).ThenReturn(map[string]k8s.Metric{"fred": k8s.Metric{}}, nil)
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List("")).ThenReturn(k8s.Collection{*k8sPod()}, nil)
|
||||
|
||||
l := resource.NewPodListWithArgs("", resource.NewPodWithArgs(ca, mx))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.AllNamespaces)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.AllNamespaces, l.GetNamespace())
|
||||
assert.True(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 11, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"blee"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestPodListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
mx := NewMockMetricsIfc()
|
||||
m.When(mx.PodMetrics()).ThenReturn(map[string]k8s.Metric{"fred": k8s.Metric{}}, nil)
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sPod(), nil)
|
||||
l := resource.NewPodListWithArgs("blee", resource.NewPodWithArgs(ca, mx))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 8, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sPod() *v1.Pod {
|
||||
var i int32 = 1
|
||||
var t = v1.HostPathDirectory
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
Labels: map[string]string{"blee": "duh"},
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Priority: &i,
|
||||
PriorityClassName: "bozo",
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "fred",
|
||||
Image: "blee",
|
||||
Env: []v1.EnvVar{
|
||||
{
|
||||
Name: "fred",
|
||||
Value: "1",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{Key: "blee"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []v1.Volume{
|
||||
{
|
||||
Name: "fred",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/blee",
|
||||
Type: &t,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: "Running",
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: "fred",
|
||||
State: v1.ContainerState{Running: &v1.ContainerStateRunning{}},
|
||||
RestartCount: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newPod() resource.Columnar {
|
||||
return resource.NewPod().NewInstance(k8sPod())
|
||||
}
|
||||
|
||||
func poYaml() string {
|
||||
return `typemeta:
|
||||
kind: Pod
|
||||
apiversion: v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels:
|
||||
blee: duh
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
volumes:
|
||||
- name: fred
|
||||
volumesource:
|
||||
hostpath:
|
||||
path: /blee
|
||||
type: Directory
|
||||
emptydir: null
|
||||
gcepersistentdisk: null
|
||||
awselasticblockstore: null
|
||||
gitrepo: null
|
||||
secret: null
|
||||
nfs: null
|
||||
iscsi: null
|
||||
glusterfs: null
|
||||
persistentvolumeclaim: null
|
||||
rbd: null
|
||||
flexvolume: null
|
||||
cinder: null
|
||||
cephfs: null
|
||||
flocker: null
|
||||
downwardapi: null
|
||||
fc: null
|
||||
azurefile: null
|
||||
configmap: null
|
||||
vspherevolume: null
|
||||
quobyte: null
|
||||
azuredisk: null
|
||||
photonpersistentdisk: null
|
||||
projected: null
|
||||
portworxvolume: null
|
||||
scaleio: null
|
||||
storageos: null
|
||||
initcontainers: []
|
||||
containers:
|
||||
- name: fred
|
||||
image: blee
|
||||
command: []
|
||||
args: []
|
||||
workingdir: ""
|
||||
ports: []
|
||||
envfrom: []
|
||||
env:
|
||||
- name: fred
|
||||
value: "1"
|
||||
valuefrom:
|
||||
fieldref: null
|
||||
resourcefieldref: null
|
||||
configmapkeyref:
|
||||
localobjectreference:
|
||||
name: ""
|
||||
key: blee
|
||||
optional: null
|
||||
secretkeyref: null
|
||||
resources:
|
||||
limits: {}
|
||||
requests: {}
|
||||
volumemounts: []
|
||||
volumedevices: []
|
||||
livenessprobe: null
|
||||
readinessprobe: null
|
||||
lifecycle: null
|
||||
terminationmessagepath: ""
|
||||
terminationmessagepolicy: ""
|
||||
imagepullpolicy: ""
|
||||
securitycontext: null
|
||||
stdin: false
|
||||
stdinonce: false
|
||||
tty: false
|
||||
restartpolicy: ""
|
||||
terminationgraceperiodseconds: null
|
||||
activedeadlineseconds: null
|
||||
dnspolicy: ""
|
||||
nodeselector: {}
|
||||
serviceaccountname: ""
|
||||
deprecatedserviceaccount: ""
|
||||
automountserviceaccounttoken: null
|
||||
nodename: ""
|
||||
hostnetwork: false
|
||||
hostpid: false
|
||||
hostipc: false
|
||||
shareprocessnamespace: null
|
||||
securitycontext: null
|
||||
imagepullsecrets: []
|
||||
hostname: ""
|
||||
subdomain: ""
|
||||
affinity: null
|
||||
schedulername: ""
|
||||
tolerations: []
|
||||
hostaliases: []
|
||||
priorityclassname: bozo
|
||||
priority: 1
|
||||
dnsconfig: null
|
||||
readinessgates: []
|
||||
runtimeclassname: null
|
||||
enableservicelinks: null
|
||||
status:
|
||||
phase: Running
|
||||
conditions: []
|
||||
message: ""
|
||||
reason: ""
|
||||
nominatednodename: ""
|
||||
hostip: ""
|
||||
podip: ""
|
||||
starttime: null
|
||||
initcontainerstatuses: []
|
||||
containerstatuses:
|
||||
- name: fred
|
||||
state:
|
||||
waiting: null
|
||||
running:
|
||||
startedat: "0001-01-01T00:00:00Z"
|
||||
terminated: null
|
||||
lastterminationstate:
|
||||
waiting: null
|
||||
running: null
|
||||
terminated: null
|
||||
ready: false
|
||||
restartcount: 0
|
||||
image: ""
|
||||
imageid: ""
|
||||
containerid: ""
|
||||
qosclass: ""
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// PV tracks a kubernetes resource.
|
||||
type PV struct {
|
||||
*Base
|
||||
instance *v1.PersistentVolume
|
||||
}
|
||||
|
||||
// NewPVList returns a new resource list.
|
||||
func NewPVList(ns string) List {
|
||||
return NewPVListWithArgs(ns, NewPV())
|
||||
}
|
||||
|
||||
// NewPVListWithArgs returns a new resource list.
|
||||
func NewPVListWithArgs(ns string, res Resource) List {
|
||||
return newList(NotNamespaced, "pv", res, CRUDAccess)
|
||||
}
|
||||
|
||||
// NewPV instantiates a new Endpoint.
|
||||
func NewPV() *PV {
|
||||
return NewPVWithArgs(k8s.NewPV())
|
||||
}
|
||||
|
||||
// NewPVWithArgs instantiates a new Endpoint.
|
||||
func NewPVWithArgs(r k8s.Res) *PV {
|
||||
ep := &PV{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ep.creator = ep
|
||||
return ep
|
||||
}
|
||||
|
||||
// NewInstance builds a new Endpoint instance from a k8s resource.
|
||||
func (*PV) NewInstance(i interface{}) Columnar {
|
||||
cm := NewPV()
|
||||
switch i.(type) {
|
||||
case *v1.PersistentVolume:
|
||||
cm.instance = i.(*v1.PersistentVolume)
|
||||
case v1.PersistentVolume:
|
||||
ii := i.(v1.PersistentVolume)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *PV) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pv := i.(*v1.PersistentVolume)
|
||||
pv.TypeMeta.APIVersion = "v1"
|
||||
pv.TypeMeta.Kind = "PV"
|
||||
raw, err := yaml.Marshal(pv)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*PV) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh, "NAME", "CAPACITY", "ACCESS MODES", "RECLAIM POLICY", "STATUS", "CLAIM", "STORAGECLASS", "REASON", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *PV) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
phase := i.Status.Phase
|
||||
if i.ObjectMeta.DeletionTimestamp != nil {
|
||||
phase = "Terminating"
|
||||
}
|
||||
|
||||
var claim string
|
||||
if i.Spec.ClaimRef != nil {
|
||||
claim = path.Join(i.Spec.ClaimRef.Namespace, i.Spec.ClaimRef.Name)
|
||||
}
|
||||
|
||||
class, found := i.Annotations[v1.BetaStorageClassAnnotation]
|
||||
if !found {
|
||||
class = i.Spec.StorageClassName
|
||||
}
|
||||
|
||||
size := i.Spec.Capacity[v1.ResourceStorage]
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
size.String(),
|
||||
r.accessMode(i.Spec.AccessModes),
|
||||
string(i.Spec.PersistentVolumeReclaimPolicy),
|
||||
string(phase),
|
||||
claim,
|
||||
class,
|
||||
i.Status.Reason,
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*PV) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func (r *PV) accessMode(aa []v1.PersistentVolumeAccessMode) string {
|
||||
dd := r.accessDedup(aa)
|
||||
s := make([]string, 0, len(dd))
|
||||
for i := 0; i < len(aa); i++ {
|
||||
switch {
|
||||
case r.accessContains(dd, v1.ReadWriteOnce):
|
||||
s = append(s, "RWO")
|
||||
case r.accessContains(dd, v1.ReadOnlyMany):
|
||||
s = append(s, "ROX")
|
||||
case r.accessContains(dd, v1.ReadWriteMany):
|
||||
s = append(s, "RWX")
|
||||
}
|
||||
}
|
||||
return strings.Join(s, ",")
|
||||
}
|
||||
|
||||
func (r *PV) accessContains(cc []v1.PersistentVolumeAccessMode, a v1.PersistentVolumeAccessMode) bool {
|
||||
for _, c := range cc {
|
||||
if c == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *PV) accessDedup(cc []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
|
||||
set := []v1.PersistentVolumeAccessMode{}
|
||||
for _, c := range cc {
|
||||
if !r.accessContains(set, c) {
|
||||
set = append(set, c)
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestPVListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewPVList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.Equal(t, "pv", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPVHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "CAPACITY", "ACCESS MODES", "RECLAIM POLICY", "STATUS", "CLAIM", "STORAGECLASS", "REASON", "AGE"}, newPV().Header("default"))
|
||||
}
|
||||
|
||||
func TestPVFields(t *testing.T) {
|
||||
r := newPV().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestPVMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sPV(), nil)
|
||||
|
||||
cm := resource.NewPVWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, pvYaml(), ma)
|
||||
}
|
||||
|
||||
func TestPVListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sPV()}, nil)
|
||||
|
||||
l := resource.NewPVListWithArgs("-", resource.NewPVWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 9, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestPVListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sPV(), nil)
|
||||
l := resource.NewPVListWithArgs("blee", resource.NewPVWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sPV() *v1.PersistentVolume {
|
||||
return &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{},
|
||||
}
|
||||
}
|
||||
|
||||
func newPV() resource.Columnar {
|
||||
return resource.NewPV().NewInstance(k8sPV())
|
||||
}
|
||||
|
||||
func pvYaml() string {
|
||||
return `typemeta:
|
||||
kind: PV
|
||||
apiversion: v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
capacity: {}
|
||||
persistentvolumesource:
|
||||
gcepersistentdisk: null
|
||||
awselasticblockstore: null
|
||||
hostpath: null
|
||||
glusterfs: null
|
||||
nfs: null
|
||||
rbd: null
|
||||
iscsi: null
|
||||
cinder: null
|
||||
cephfs: null
|
||||
fc: null
|
||||
flocker: null
|
||||
flexvolume: null
|
||||
azurefile: null
|
||||
vspherevolume: null
|
||||
quobyte: null
|
||||
azuredisk: null
|
||||
photonpersistentdisk: null
|
||||
portworxvolume: null
|
||||
scaleio: null
|
||||
local: null
|
||||
storageos: null
|
||||
csi: null
|
||||
accessmodes: []
|
||||
claimref: null
|
||||
persistentvolumereclaimpolicy: ""
|
||||
storageclassname: ""
|
||||
mountoptions: []
|
||||
volumemode: null
|
||||
nodeaffinity: null
|
||||
status:
|
||||
phase: ""
|
||||
message: ""
|
||||
reason: ""
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// PVC tracks a kubernetes resource.
|
||||
type PVC struct {
|
||||
*Base
|
||||
instance *v1.PersistentVolumeClaim
|
||||
}
|
||||
|
||||
// NewPVCList returns a new resource list.
|
||||
func NewPVCList(ns string) List {
|
||||
return NewPVCListWithArgs(ns, NewPVC())
|
||||
}
|
||||
|
||||
// NewPVCListWithArgs returns a new resource list.
|
||||
func NewPVCListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "pvc", res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewPVC instantiates a new Endpoint.
|
||||
func NewPVC() *PVC {
|
||||
return NewPVCWithArgs(k8s.NewPVC())
|
||||
}
|
||||
|
||||
// NewPVCWithArgs instantiates a new Endpoint.
|
||||
func NewPVCWithArgs(r k8s.Res) *PVC {
|
||||
ep := &PVC{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ep.creator = ep
|
||||
return ep
|
||||
}
|
||||
|
||||
// NewInstance builds a new Endpoint instance from a k8s resource.
|
||||
func (*PVC) NewInstance(i interface{}) Columnar {
|
||||
cm := NewPVC()
|
||||
switch i.(type) {
|
||||
case *v1.PersistentVolumeClaim:
|
||||
cm.instance = i.(*v1.PersistentVolumeClaim)
|
||||
case v1.PersistentVolumeClaim:
|
||||
ii := i.(v1.PersistentVolumeClaim)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *PVC) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pvc := i.(*v1.PersistentVolumeClaim)
|
||||
pvc.TypeMeta.APIVersion = "v1"
|
||||
pvc.TypeMeta.Kind = "PersistentVolumeClaim"
|
||||
raw, err := yaml.Marshal(pvc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*PVC) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh, "NAME", "STATUS", "VOLUME", "CAPACITY", "ACCESS MODES", "STORAGECLASS", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *PVC) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
phase := i.Status.Phase
|
||||
if i.ObjectMeta.DeletionTimestamp != nil {
|
||||
phase = "Terminating"
|
||||
}
|
||||
|
||||
pv := PV{}
|
||||
storage := i.Spec.Resources.Requests[v1.ResourceStorage]
|
||||
var capacity, accessModes string
|
||||
if i.Spec.VolumeName != "" {
|
||||
accessModes = pv.accessMode(i.Status.AccessModes)
|
||||
storage = i.Status.Capacity[v1.ResourceStorage]
|
||||
capacity = storage.String()
|
||||
}
|
||||
|
||||
class, found := i.Annotations[v1.BetaStorageClassAnnotation]
|
||||
if !found {
|
||||
if i.Spec.StorageClassName != nil {
|
||||
class = *i.Spec.StorageClassName
|
||||
}
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
string(phase),
|
||||
i.Spec.VolumeName,
|
||||
capacity,
|
||||
accessModes,
|
||||
class,
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*PVC) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/core/v1"
|
||||
resv1 "k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestPVCListAccess(t *testing.T) {
|
||||
ns := "blee"
|
||||
l := resource.NewPVCList(resource.AllNamespaces)
|
||||
l.SetNamespace(ns)
|
||||
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.Equal(t, "pvc", l.GetName())
|
||||
for _, a := range []int{resource.GetAccess, resource.ListAccess, resource.DeleteAccess, resource.ViewAccess, resource.EditAccess} {
|
||||
assert.True(t, l.Access(a))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPVCHeader(t *testing.T) {
|
||||
assert.Equal(t, resource.Row{"NAME", "STATUS", "VOLUME", "CAPACITY", "ACCESS MODES", "STORAGECLASS", "AGE"}, newPVC().Header("default"))
|
||||
}
|
||||
|
||||
func TestPVCFields(t *testing.T) {
|
||||
r := newPVC().Fields("blee")
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestPVCMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sPVC(), nil)
|
||||
|
||||
cm := resource.NewPVCWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, pvcYaml(), ma)
|
||||
}
|
||||
|
||||
func TestPVCListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List(resource.NotNamespaced)).ThenReturn(k8s.Collection{*k8sPVC()}, nil)
|
||||
|
||||
l := resource.NewPVCListWithArgs("-", resource.NewPVCWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List(resource.NotNamespaced)
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 7, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestPVCListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sPVC(), nil)
|
||||
l := resource.NewPVCListWithArgs("blee", resource.NewPVCWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sPVC() *v1.PersistentVolumeClaim {
|
||||
return &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
VolumeName: "duh",
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceStorage: resv1.Quantity{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newPVC() resource.Columnar {
|
||||
return resource.NewPVC().NewInstance(k8sPVC())
|
||||
}
|
||||
|
||||
func pvcYaml() string {
|
||||
return `typemeta:
|
||||
kind: PersistentVolumeClaim
|
||||
apiversion: v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
spec:
|
||||
accessmodes: []
|
||||
selector: null
|
||||
resources:
|
||||
limits: {}
|
||||
requests:
|
||||
storage:
|
||||
format: ""
|
||||
volumename: duh
|
||||
storageclassname: null
|
||||
volumemode: null
|
||||
datasource: null
|
||||
status:
|
||||
phase: ""
|
||||
accessmodes: []
|
||||
capacity: {}
|
||||
conditions: []
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/api/rbac/v1"
|
||||
)
|
||||
|
||||
// Role tracks a kubernetes resource.
|
||||
type Role struct {
|
||||
*Base
|
||||
instance *v1.Role
|
||||
}
|
||||
|
||||
// NewRoleList returns a new resource list.
|
||||
func NewRoleList(ns string) List {
|
||||
return NewRoleListWithArgs(ns, NewRole())
|
||||
}
|
||||
|
||||
// NewRoleListWithArgs returns a new resource list.
|
||||
func NewRoleListWithArgs(ns string, res Resource) List {
|
||||
l := newList(ns, "role", res, AllVerbsAccess|DescribeAccess)
|
||||
l.xray = true
|
||||
return l
|
||||
}
|
||||
|
||||
// NewRole instantiates a new Endpoint.
|
||||
func NewRole() *Role {
|
||||
return NewRoleWithArgs(k8s.NewRole())
|
||||
}
|
||||
|
||||
// NewRoleWithArgs instantiates a new Endpoint.
|
||||
func NewRoleWithArgs(r k8s.Res) *Role {
|
||||
ep := &Role{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ep.creator = ep
|
||||
return ep
|
||||
}
|
||||
|
||||
// NewInstance builds a new Endpoint instance from a k8s resource.
|
||||
func (*Role) NewInstance(i interface{}) Columnar {
|
||||
cm := NewRole()
|
||||
switch i.(type) {
|
||||
case *v1.Role:
|
||||
cm.instance = i.(*v1.Role)
|
||||
case v1.Role:
|
||||
ii := i.(v1.Role)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *Role) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
role := i.(*v1.Role)
|
||||
role.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1"
|
||||
role.TypeMeta.Kind = "Role"
|
||||
raw, err := yaml.Marshal(role)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*Role) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh, "NAME", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *Role) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (r *Role) ExtFields() Properties {
|
||||
i := r.instance
|
||||
|
||||
return Properties{
|
||||
"Headers": Row{"RESOURCES", "NON-RESOURCE URLS", "RESOURCE NAMES", "VERBS"},
|
||||
"Rows": r.parseRules(i.Rules),
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func (r *Role) parseRules(pp []v1.PolicyRule) []Row {
|
||||
acc := make([]Row, len(pp))
|
||||
for i, p := range pp {
|
||||
acc[i] = make(Row, 4)
|
||||
acc[i][0] = strings.Join(p.Resources, ", ")
|
||||
acc[i][1] = strings.Join(p.NonResourceURLs, ", ")
|
||||
acc[i][2] = strings.Join(p.ResourceNames, ", ")
|
||||
acc[i][3] = strings.Join(p.Verbs, ", ")
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
"k8s.io/api/rbac/v1"
|
||||
)
|
||||
|
||||
// RoleBinding tracks a kubernetes resource.
|
||||
type RoleBinding struct {
|
||||
*Base
|
||||
instance *v1.RoleBinding
|
||||
}
|
||||
|
||||
// NewRoleBindingList returns a new resource list.
|
||||
func NewRoleBindingList(ns string) List {
|
||||
return NewRoleBindingListWithArgs(ns, NewRoleBinding())
|
||||
}
|
||||
|
||||
// NewRoleBindingListWithArgs returns a new resource list.
|
||||
func NewRoleBindingListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "rolebinding", res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewRoleBinding instantiates a new Endpoint.
|
||||
func NewRoleBinding() *RoleBinding {
|
||||
return NewRoleBindingWithArgs(k8s.NewRoleBinding())
|
||||
}
|
||||
|
||||
// NewRoleBindingWithArgs instantiates a new Endpoint.
|
||||
func NewRoleBindingWithArgs(r k8s.Res) *RoleBinding {
|
||||
ep := &RoleBinding{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ep.creator = ep
|
||||
return ep
|
||||
}
|
||||
|
||||
// NewInstance builds a new Endpoint instance from a k8s resource.
|
||||
func (*RoleBinding) NewInstance(i interface{}) Columnar {
|
||||
cm := NewRoleBinding()
|
||||
switch i.(type) {
|
||||
case *v1.RoleBinding:
|
||||
cm.instance = i.(*v1.RoleBinding)
|
||||
case v1.RoleBinding:
|
||||
ii := i.(v1.RoleBinding)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal resource to yaml.
|
||||
func (r *RoleBinding) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rb := i.(*v1.RoleBinding)
|
||||
rb.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1"
|
||||
rb.TypeMeta.Kind = "RoleBinding"
|
||||
raw, err := yaml.Marshal(rb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*RoleBinding) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh, "NAME", "ROLE", "SUBJECTS", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *RoleBinding) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
i.RoleRef.Name,
|
||||
r.toSubjects(i.Subjects),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*RoleBinding) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func (r *RoleBinding) toSubjects(ss []v1.Subject) string {
|
||||
var acc string
|
||||
for i, s := range ss {
|
||||
acc += s.Name + "/" + r.toSubjectAlias(s.Kind)
|
||||
if i < len(ss)-1 {
|
||||
acc += ","
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
func (r *RoleBinding) toSubjectAlias(s string) string {
|
||||
switch s {
|
||||
case v1.UserKind:
|
||||
return "USR"
|
||||
case v1.GroupKind:
|
||||
return "GRP"
|
||||
case v1.ServiceAccountKind:
|
||||
return "SA"
|
||||
default:
|
||||
return strings.ToUpper(s)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
)
|
||||
|
||||
func TestToSubjectAlias(t *testing.T) {
|
||||
r := RoleBinding{}
|
||||
|
||||
uu := []struct {
|
||||
i string
|
||||
e string
|
||||
}{
|
||||
{rbacv1.UserKind, "USR"},
|
||||
{rbacv1.GroupKind, "GRP"},
|
||||
{rbacv1.ServiceAccountKind, "SA"},
|
||||
{"fred", "FRED"},
|
||||
}
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, r.toSubjectAlias(u.i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSubjects(t *testing.T) {
|
||||
r := RoleBinding{}
|
||||
|
||||
uu := []struct {
|
||||
i []rbacv1.Subject
|
||||
e string
|
||||
}{
|
||||
{
|
||||
[]rbacv1.Subject{
|
||||
{Name: "blee", Kind: rbacv1.UserKind},
|
||||
},
|
||||
"blee/USR",
|
||||
},
|
||||
}
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, r.toSubjects(u.i))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkToSubjects(b *testing.B) {
|
||||
var r RoleBinding
|
||||
ss := []rbacv1.Subject{
|
||||
{Name: "blee", Kind: rbacv1.UserKind},
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.toSubjects(ss)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestRBMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sRB(), nil)
|
||||
|
||||
cm := resource.NewRoleBindingWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, rbYaml(), ma)
|
||||
}
|
||||
|
||||
func TestRBListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List("blee")).ThenReturn(k8s.Collection{*k8sRB()}, nil)
|
||||
|
||||
l := resource.NewRoleBindingListWithArgs("blee", resource.NewRoleBindingWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List("blee")
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.False(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 4, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestRBListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sRB(), nil)
|
||||
l := resource.NewRoleBindingListWithArgs("blee", resource.NewRoleBindingWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sRB() *v1.RoleBinding {
|
||||
return &v1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
Subjects: []v1.Subject{
|
||||
{
|
||||
Kind: v1.UserKind,
|
||||
Name: "fred",
|
||||
Namespace: "blee",
|
||||
},
|
||||
},
|
||||
RoleRef: v1.RoleRef{
|
||||
Kind: v1.UserKind,
|
||||
Name: "duh",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newRB() resource.Columnar {
|
||||
return resource.NewRoleBinding().NewInstance(k8sRB())
|
||||
}
|
||||
|
||||
func rbYaml() string {
|
||||
return `typemeta:
|
||||
kind: RoleBinding
|
||||
apiversion: rbac.authorization.k8s.io/v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
subjects:
|
||||
- kind: User
|
||||
apigroup: ""
|
||||
name: fred
|
||||
namespace: blee
|
||||
roleref:
|
||||
apigroup: ""
|
||||
kind: User
|
||||
name: duh
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package resource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/k8sland/k9s/resource"
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
m "github.com/petergtz/pegomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestRoleMarshal(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sRole(), nil)
|
||||
|
||||
cm := resource.NewRoleWithArgs(ca)
|
||||
ma, err := cm.Marshal("blee/fred")
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, roleYaml(), ma)
|
||||
}
|
||||
|
||||
func TestRoleListData(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.List("blee")).ThenReturn(k8s.Collection{*k8sRole()}, nil)
|
||||
|
||||
l := resource.NewRoleListWithArgs("blee", resource.NewRoleWithArgs(ca))
|
||||
// Make sure we can get deltas!
|
||||
for i := 0; i < 2; i++ {
|
||||
err := l.Reconcile()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ca.VerifyWasCalled(m.Times(2)).List("blee")
|
||||
td := l.Data()
|
||||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
assert.True(t, l.HasXRay())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 2, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
func TestRoleListDescribe(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
ca := NewMockCaller()
|
||||
m.When(ca.Get("blee", "fred")).ThenReturn(k8sRole(), nil)
|
||||
l := resource.NewRoleListWithArgs("blee", resource.NewRoleWithArgs(ca))
|
||||
props, err := l.Describe("blee/fred")
|
||||
|
||||
ca.VerifyWasCalledOnce().Get("blee", "fred")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(props))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func k8sRole() *v1.Role {
|
||||
return &v1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "blee",
|
||||
Name: "fred",
|
||||
CreationTimestamp: metav1.Time{Time: testTime()},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newRole() resource.Columnar {
|
||||
return resource.NewRole().NewInstance(k8sRole())
|
||||
}
|
||||
|
||||
func roleYaml() string {
|
||||
return `typemeta:
|
||||
kind: Role
|
||||
apiversion: rbac.authorization.k8s.io/v1
|
||||
objectmeta:
|
||||
name: fred
|
||||
generatename: ""
|
||||
namespace: blee
|
||||
selflink: ""
|
||||
uid: ""
|
||||
resourceversion: ""
|
||||
generation: 0
|
||||
creationtimestamp: "2018-12-14T10:36:43.326972-07:00"
|
||||
deletiontimestamp: null
|
||||
deletiongraceperiodseconds: null
|
||||
labels: {}
|
||||
annotations: {}
|
||||
ownerreferences: []
|
||||
initializers: null
|
||||
finalizers: []
|
||||
clustername: ""
|
||||
rules: []
|
||||
`
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/k8sland/k9s/resource/k8s"
|
||||
log "github.com/sirupsen/logrus"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"k8s.io/api/apps/v1"
|
||||
)
|
||||
|
||||
// ReplicaSet tracks a kubernetes resource.
|
||||
type ReplicaSet struct {
|
||||
*Base
|
||||
instance *v1.ReplicaSet
|
||||
}
|
||||
|
||||
// NewReplicaSetList returns a new resource list.
|
||||
func NewReplicaSetList(ns string) List {
|
||||
return NewReplicaSetListWithArgs(ns, NewReplicaSet())
|
||||
}
|
||||
|
||||
// NewReplicaSetListWithArgs returns a new resource list.
|
||||
func NewReplicaSetListWithArgs(ns string, res Resource) List {
|
||||
return newList(ns, "rs", res, AllVerbsAccess)
|
||||
}
|
||||
|
||||
// NewReplicaSet instantiates a new Endpoint.
|
||||
func NewReplicaSet() *ReplicaSet {
|
||||
return NewReplicaSetWithArgs(k8s.NewReplicaSet())
|
||||
}
|
||||
|
||||
// NewReplicaSetWithArgs instantiates a new Endpoint.
|
||||
func NewReplicaSetWithArgs(r k8s.Res) *ReplicaSet {
|
||||
ep := &ReplicaSet{
|
||||
Base: &Base{
|
||||
caller: r,
|
||||
},
|
||||
}
|
||||
ep.creator = ep
|
||||
return ep
|
||||
}
|
||||
|
||||
// NewInstance builds a new Endpoint instance from a k8s resource.
|
||||
func (*ReplicaSet) NewInstance(i interface{}) Columnar {
|
||||
cm := NewReplicaSet()
|
||||
switch i.(type) {
|
||||
case *v1.ReplicaSet:
|
||||
cm.instance = i.(*v1.ReplicaSet)
|
||||
case v1.ReplicaSet:
|
||||
ii := i.(v1.ReplicaSet)
|
||||
cm.instance = &ii
|
||||
default:
|
||||
log.Fatalf("Unknown %#v", i)
|
||||
}
|
||||
cm.path = cm.namespacedName(cm.instance.ObjectMeta)
|
||||
return cm
|
||||
}
|
||||
|
||||
// Marshal a deployment given a namespaced name.
|
||||
func (r *ReplicaSet) Marshal(path string) (string, error) {
|
||||
ns, n := namespaced(path)
|
||||
i, err := r.caller.Get(ns, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rs := i.(*v1.ReplicaSet)
|
||||
rs.TypeMeta.APIVersion = "extensions/v1beta"
|
||||
rs.TypeMeta.Kind = "ReplicaSet"
|
||||
raw, err := yaml.Marshal(rs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(raw), nil
|
||||
}
|
||||
|
||||
// Header return resource header.
|
||||
func (*ReplicaSet) Header(ns string) Row {
|
||||
hh := Row{}
|
||||
if ns == AllNamespaces {
|
||||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
return append(hh, "NAME", "DESIRED", "CURRENT", "READY", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *ReplicaSet) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, r.instance.Namespace)
|
||||
}
|
||||
|
||||
i := r.instance
|
||||
return append(ff,
|
||||
i.Name,
|
||||
strconv.Itoa(int(*i.Spec.Replicas)),
|
||||
strconv.Itoa(int(i.Status.Replicas)),
|
||||
strconv.Itoa(int(i.Status.ReadyReplicas)),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtFields returns extended fields in relation to headers.
|
||||
func (*ReplicaSet) ExtFields() Properties {
|
||||
return Properties{}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue