mirror of
https://github.com/weaveworks/scope.git
synced 2026-05-09 10:47:27 +00:00
Scope will no longer work with Kubernetes 1.8 and below. For CronJob there is no 'v1' as yet, but we can remove the alpha version.
627 lines
19 KiB
Go
627 lines
19 KiB
Go
package kubernetes
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/weaveworks/common/backoff"
|
|
|
|
snapshotv1 "github.com/openebs/k8s-snapshot-client/snapshot/pkg/apis/volumesnapshot/v1"
|
|
snapshot "github.com/openebs/k8s-snapshot-client/snapshot/pkg/client/clientset/versioned"
|
|
"github.com/pborman/uuid"
|
|
log "github.com/sirupsen/logrus"
|
|
apiappsv1 "k8s.io/api/apps/v1"
|
|
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
|
apibatchv1 "k8s.io/api/batch/v1"
|
|
apibatchv1beta1 "k8s.io/api/batch/v1beta1"
|
|
apiv1 "k8s.io/api/core/v1"
|
|
storagev1 "k8s.io/api/storage/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/fields"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/client-go/tools/cache"
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
|
kubectldescribe "k8s.io/kubernetes/pkg/kubectl/describe"
|
|
kubectl "k8s.io/kubernetes/pkg/kubectl/describe/versioned"
|
|
)
|
|
|
|
// Client keeps track of running kubernetes pods and services
|
|
type Client interface {
|
|
Stop()
|
|
WalkPods(f func(Pod) error) error
|
|
WalkServices(f func(Service) error) error
|
|
WalkDeployments(f func(Deployment) error) error
|
|
WalkDaemonSets(f func(DaemonSet) error) error
|
|
WalkStatefulSets(f func(StatefulSet) error) error
|
|
WalkCronJobs(f func(CronJob) error) error
|
|
WalkNamespaces(f func(NamespaceResource) error) error
|
|
WalkPersistentVolumes(f func(PersistentVolume) error) error
|
|
WalkPersistentVolumeClaims(f func(PersistentVolumeClaim) error) error
|
|
WalkStorageClasses(f func(StorageClass) error) error
|
|
WalkVolumeSnapshots(f func(VolumeSnapshot) error) error
|
|
WalkVolumeSnapshotData(f func(VolumeSnapshotData) error) error
|
|
WalkJobs(f func(Job) error) error
|
|
|
|
WatchPods(f func(Event, Pod))
|
|
|
|
CloneVolumeSnapshot(namespaceID, volumeSnapshotID, persistentVolumeClaimID, capacity string) error
|
|
CreateVolumeSnapshot(namespaceID, persistentVolumeClaimID, capacity string) error
|
|
GetLogs(namespaceID, podID string, containerNames []string) (io.ReadCloser, error)
|
|
Describe(namespaceID, resourceID string, groupKind schema.GroupKind, restMapping apimeta.RESTMapping) (io.ReadCloser, error)
|
|
DeletePod(namespaceID, podID string) error
|
|
DeleteVolumeSnapshot(namespaceID, volumeSnapshotID string) error
|
|
ScaleUp(namespaceID, id string) error
|
|
ScaleDown(namespaceID, id string) error
|
|
}
|
|
|
|
// ResourceMap is the mapping of resource and their GroupKind
|
|
var ResourceMap = map[string]schema.GroupKind{
|
|
"Pod": {Group: apiv1.GroupName, Kind: "Pod"},
|
|
"Service": {Group: apiv1.GroupName, Kind: "Service"},
|
|
"Deployment": {Group: apiappsv1.GroupName, Kind: "Deployment"},
|
|
"DaemonSet": {Group: apiappsv1.GroupName, Kind: "DaemonSet"},
|
|
"StatefulSet": {Group: apiappsv1.GroupName, Kind: "StatefulSet"},
|
|
"Job": {Group: apibatchv1.GroupName, Kind: "Job"},
|
|
"CronJob": {Group: apibatchv1.GroupName, Kind: "CronJob"},
|
|
"Node": {Group: apiv1.GroupName, Kind: "Node"},
|
|
"PersistentVolume": {Group: apiv1.GroupName, Kind: "PersistentVolume"},
|
|
"PersistentVolumeClaim": {Group: apiv1.GroupName, Kind: "PersistentVolumeClaim"},
|
|
"StorageClass": {Group: storagev1.GroupName, Kind: "StorageClass"},
|
|
}
|
|
|
|
type client struct {
|
|
quit chan struct{}
|
|
client *kubernetes.Clientset
|
|
snapshotClient *snapshot.Clientset
|
|
podStore cache.Store
|
|
serviceStore cache.Store
|
|
deploymentStore cache.Store
|
|
daemonSetStore cache.Store
|
|
statefulSetStore cache.Store
|
|
jobStore cache.Store
|
|
cronJobStore cache.Store
|
|
nodeStore cache.Store
|
|
namespaceStore cache.Store
|
|
persistentVolumeStore cache.Store
|
|
persistentVolumeClaimStore cache.Store
|
|
storageClassStore cache.Store
|
|
volumeSnapshotStore cache.Store
|
|
volumeSnapshotDataStore cache.Store
|
|
|
|
podWatchesMutex sync.Mutex
|
|
podWatches []func(Event, Pod)
|
|
}
|
|
|
|
// ClientConfig establishes the configuration for the kubernetes client
|
|
type ClientConfig struct {
|
|
CertificateAuthority string
|
|
ClientCertificate string
|
|
ClientKey string
|
|
Cluster string
|
|
Context string
|
|
Insecure bool
|
|
Kubeconfig string
|
|
Password string
|
|
Server string
|
|
Token string
|
|
User string
|
|
Username string
|
|
}
|
|
|
|
// NewClient returns a usable Client. Don't forget to Stop it.
|
|
func NewClient(config ClientConfig) (Client, error) {
|
|
var restConfig *rest.Config
|
|
if config.Server == "" && config.Kubeconfig == "" {
|
|
// If no API server address or kubeconfig was provided, assume we are running
|
|
// inside a pod. Try to connect to the API server through its
|
|
// Service environment variables, using the default Service
|
|
// Account Token.
|
|
var err error
|
|
if restConfig, err = rest.InClusterConfig(); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
var err error
|
|
restConfig, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
|
&clientcmd.ClientConfigLoadingRules{ExplicitPath: config.Kubeconfig},
|
|
&clientcmd.ConfigOverrides{
|
|
AuthInfo: clientcmdapi.AuthInfo{
|
|
ClientCertificate: config.ClientCertificate,
|
|
ClientKey: config.ClientKey,
|
|
Token: config.Token,
|
|
Username: config.Username,
|
|
Password: config.Password,
|
|
},
|
|
ClusterInfo: clientcmdapi.Cluster{
|
|
Server: config.Server,
|
|
InsecureSkipTLSVerify: config.Insecure,
|
|
CertificateAuthority: config.CertificateAuthority,
|
|
},
|
|
Context: clientcmdapi.Context{
|
|
Cluster: config.Cluster,
|
|
AuthInfo: config.User,
|
|
},
|
|
CurrentContext: config.Context,
|
|
},
|
|
).ClientConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
log.Infof("kubernetes: targeting api server %s", restConfig.Host)
|
|
|
|
c, err := kubernetes.NewForConfig(restConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sc, err := snapshot.NewForConfig(restConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := &client{
|
|
quit: make(chan struct{}),
|
|
client: c,
|
|
snapshotClient: sc,
|
|
}
|
|
|
|
result.podStore = NewEventStore(result.triggerPodWatches, cache.MetaNamespaceKeyFunc)
|
|
result.runReflectorUntil("pods", result.podStore)
|
|
|
|
result.serviceStore = result.setupStore("services")
|
|
result.nodeStore = result.setupStore("nodes")
|
|
result.namespaceStore = result.setupStore("namespaces")
|
|
result.deploymentStore = result.setupStore("deployments")
|
|
result.daemonSetStore = result.setupStore("daemonsets")
|
|
result.jobStore = result.setupStore("jobs")
|
|
result.statefulSetStore = result.setupStore("statefulsets")
|
|
result.cronJobStore = result.setupStore("cronjobs")
|
|
result.persistentVolumeStore = result.setupStore("persistentvolumes")
|
|
result.persistentVolumeClaimStore = result.setupStore("persistentvolumeclaims")
|
|
result.storageClassStore = result.setupStore("storageclasses")
|
|
result.volumeSnapshotStore = result.setupStore("volumesnapshots")
|
|
result.volumeSnapshotDataStore = result.setupStore("volumesnapshotdatas")
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (c *client) isResourceSupported(groupVersion schema.GroupVersion, resource string) (bool, error) {
|
|
resourceList, err := c.client.Discovery().ServerResourcesForGroupVersion(groupVersion.String())
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
for _, v := range resourceList.APIResources {
|
|
if v.Name == resource {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (c *client) setupStore(resource string) cache.Store {
|
|
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
|
c.runReflectorUntil(resource, store)
|
|
return store
|
|
}
|
|
|
|
func (c *client) clientAndType(resource string) (rest.Interface, interface{}, error) {
|
|
switch resource {
|
|
case "pods":
|
|
return c.client.CoreV1().RESTClient(), &apiv1.Pod{}, nil
|
|
case "services":
|
|
return c.client.CoreV1().RESTClient(), &apiv1.Service{}, nil
|
|
case "nodes":
|
|
return c.client.CoreV1().RESTClient(), &apiv1.Node{}, nil
|
|
case "namespaces":
|
|
return c.client.CoreV1().RESTClient(), &apiv1.Namespace{}, nil
|
|
case "persistentvolumes":
|
|
return c.client.CoreV1().RESTClient(), &apiv1.PersistentVolume{}, nil
|
|
case "persistentvolumeclaims":
|
|
return c.client.CoreV1().RESTClient(), &apiv1.PersistentVolumeClaim{}, nil
|
|
case "storageclasses":
|
|
return c.client.StorageV1().RESTClient(), &storagev1.StorageClass{}, nil
|
|
case "deployments":
|
|
return c.client.AppsV1().RESTClient(), &apiappsv1.Deployment{}, nil
|
|
case "daemonsets":
|
|
return c.client.AppsV1().RESTClient(), &apiappsv1.DaemonSet{}, nil
|
|
case "jobs":
|
|
return c.client.BatchV1().RESTClient(), &apibatchv1.Job{}, nil
|
|
case "statefulsets":
|
|
return c.client.AppsV1().RESTClient(), &apiappsv1.StatefulSet{}, nil
|
|
case "volumesnapshots":
|
|
return c.snapshotClient.VolumesnapshotV1().RESTClient(), &snapshotv1.VolumeSnapshot{}, nil
|
|
case "volumesnapshotdatas":
|
|
return c.snapshotClient.VolumesnapshotV1().RESTClient(), &snapshotv1.VolumeSnapshotData{}, nil
|
|
case "cronjobs":
|
|
return c.client.BatchV1beta1().RESTClient(), &apibatchv1beta1.CronJob{}, nil
|
|
}
|
|
return nil, nil, fmt.Errorf("Invalid resource: %v", resource)
|
|
}
|
|
|
|
// runReflectorUntil runs cache.Reflector#ListAndWatch in an endless loop, after checking that the resource is supported by kubernetes.
|
|
// Errors are logged and retried with exponential backoff.
|
|
func (c *client) runReflectorUntil(resource string, store cache.Store) {
|
|
var r *cache.Reflector
|
|
listAndWatch := func() (bool, error) {
|
|
if r == nil {
|
|
kclient, itemType, err := c.clientAndType(resource)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
ok, err := c.isResourceSupported(kclient.APIVersion(), resource)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if !ok {
|
|
log.Infof("%v are not supported by this Kubernetes version", resource)
|
|
return true, nil
|
|
}
|
|
lw := cache.NewListWatchFromClient(kclient, resource, metav1.NamespaceAll, fields.Everything())
|
|
r = cache.NewReflector(lw, itemType, store, 0)
|
|
}
|
|
|
|
select {
|
|
case <-c.quit:
|
|
return true, nil
|
|
default:
|
|
err := r.ListAndWatch(c.quit)
|
|
return false, err
|
|
}
|
|
}
|
|
bo := backoff.New(listAndWatch, fmt.Sprintf("Kubernetes reflector (%s)", resource))
|
|
bo.SetMaxBackoff(5 * time.Minute)
|
|
go bo.Start()
|
|
}
|
|
|
|
func (c *client) WatchPods(f func(Event, Pod)) {
|
|
c.podWatchesMutex.Lock()
|
|
defer c.podWatchesMutex.Unlock()
|
|
c.podWatches = append(c.podWatches, f)
|
|
}
|
|
|
|
func (c *client) triggerPodWatches(e Event, pod interface{}) {
|
|
c.podWatchesMutex.Lock()
|
|
defer c.podWatchesMutex.Unlock()
|
|
for _, watch := range c.podWatches {
|
|
watch(e, NewPod(pod.(*apiv1.Pod)))
|
|
}
|
|
}
|
|
|
|
func (c *client) WalkPods(f func(Pod) error) error {
|
|
for _, m := range c.podStore.List() {
|
|
pod := m.(*apiv1.Pod)
|
|
if err := f(NewPod(pod)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) WalkPersistentVolumes(f func(PersistentVolume) error) error {
|
|
for _, m := range c.persistentVolumeStore.List() {
|
|
pv := m.(*apiv1.PersistentVolume)
|
|
if err := f(NewPersistentVolume(pv)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) WalkPersistentVolumeClaims(f func(PersistentVolumeClaim) error) error {
|
|
for _, m := range c.persistentVolumeClaimStore.List() {
|
|
pvc := m.(*apiv1.PersistentVolumeClaim)
|
|
if err := f(NewPersistentVolumeClaim(pvc)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) WalkStorageClasses(f func(StorageClass) error) error {
|
|
for _, m := range c.storageClassStore.List() {
|
|
sc := m.(*storagev1.StorageClass)
|
|
if err := f(NewStorageClass(sc)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) WalkServices(f func(Service) error) error {
|
|
for _, m := range c.serviceStore.List() {
|
|
s := m.(*apiv1.Service)
|
|
if err := f(NewService(s)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) WalkDeployments(f func(Deployment) error) error {
|
|
if c.deploymentStore == nil {
|
|
return nil
|
|
}
|
|
for _, m := range c.deploymentStore.List() {
|
|
d := m.(*apiappsv1.Deployment)
|
|
if err := f(NewDeployment(d)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WalkDaemonSets calls f for each daemonset
|
|
func (c *client) WalkDaemonSets(f func(DaemonSet) error) error {
|
|
if c.daemonSetStore == nil {
|
|
return nil
|
|
}
|
|
for _, m := range c.daemonSetStore.List() {
|
|
ds := m.(*apiappsv1.DaemonSet)
|
|
if err := f(NewDaemonSet(ds)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WalkStatefulSets calls f for each statefulset
|
|
func (c *client) WalkStatefulSets(f func(StatefulSet) error) error {
|
|
if c.statefulSetStore == nil {
|
|
return nil
|
|
}
|
|
for _, m := range c.statefulSetStore.List() {
|
|
s := m.(*apiappsv1.StatefulSet)
|
|
if err := f(NewStatefulSet(s)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WalkCronJobs calls f for each cronjob
|
|
func (c *client) WalkCronJobs(f func(CronJob) error) error {
|
|
if c.cronJobStore == nil {
|
|
return nil
|
|
}
|
|
// We index jobs by id to make lookup for each cronjob more efficient
|
|
jobs := map[types.UID]*apibatchv1.Job{}
|
|
for _, m := range c.jobStore.List() {
|
|
j := m.(*apibatchv1.Job)
|
|
jobs[j.UID] = j
|
|
}
|
|
for _, m := range c.cronJobStore.List() {
|
|
cj := m.(*apibatchv1beta1.CronJob)
|
|
if err := f(NewCronJob(cj, jobs)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) WalkNamespaces(f func(NamespaceResource) error) error {
|
|
for _, m := range c.namespaceStore.List() {
|
|
namespace := m.(*apiv1.Namespace)
|
|
if err := f(NewNamespace(namespace)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) WalkVolumeSnapshots(f func(VolumeSnapshot) error) error {
|
|
for _, m := range c.volumeSnapshotStore.List() {
|
|
volumeSnapshot := m.(*snapshotv1.VolumeSnapshot)
|
|
if err := f(NewVolumeSnapshot(volumeSnapshot)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) WalkVolumeSnapshotData(f func(VolumeSnapshotData) error) error {
|
|
for _, m := range c.volumeSnapshotDataStore.List() {
|
|
volumeSnapshotData := m.(*snapshotv1.VolumeSnapshotData)
|
|
if err := f(NewVolumeSnapshotData(volumeSnapshotData)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) WalkJobs(f func(Job) error) error {
|
|
for _, m := range c.jobStore.List() {
|
|
job := m.(*apibatchv1.Job)
|
|
if err := f(NewJob(job)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) CloneVolumeSnapshot(namespaceID, volumeSnapshotID, persistentVolumeClaimID, capacity string) error {
|
|
var scName string
|
|
var claimSize string
|
|
UID := strings.Split(uuid.New(), "-")
|
|
scProvisionerName := "volumesnapshot.external-storage.k8s.io/snapshot-promoter"
|
|
scList, err := c.client.StorageV1().StorageClasses().List(metav1.ListOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Retrieve the first snapshot-promoter storage class
|
|
for _, sc := range scList.Items {
|
|
if sc.Provisioner == scProvisionerName {
|
|
scName = sc.Name
|
|
break
|
|
}
|
|
}
|
|
if scName == "" {
|
|
return errors.New("snapshot-promoter storage class is not present")
|
|
}
|
|
volumeSnapshot, _ := c.snapshotClient.VolumesnapshotV1().VolumeSnapshots(namespaceID).Get(volumeSnapshotID, metav1.GetOptions{})
|
|
if volumeSnapshot.Spec.PersistentVolumeClaimName != "" {
|
|
persistentVolumeClaim, err := c.client.CoreV1().PersistentVolumeClaims(namespaceID).Get(volumeSnapshot.Spec.PersistentVolumeClaimName, metav1.GetOptions{})
|
|
if err == nil {
|
|
storage := persistentVolumeClaim.Spec.Resources.Requests[apiv1.ResourceStorage]
|
|
if storage.String() != "" {
|
|
claimSize = storage.String()
|
|
}
|
|
}
|
|
}
|
|
// Set default volume size to the one stored in volume snapshot annotation,
|
|
// if unable to get PVC size.
|
|
if claimSize == "" {
|
|
claimSize = capacity
|
|
}
|
|
|
|
persistentVolumeClaim := &apiv1.PersistentVolumeClaim{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "clone-" + persistentVolumeClaimID + "-" + UID[1],
|
|
Namespace: namespaceID,
|
|
Annotations: map[string]string{
|
|
"snapshot.alpha.kubernetes.io/snapshot": volumeSnapshotID,
|
|
},
|
|
},
|
|
Spec: apiv1.PersistentVolumeClaimSpec{
|
|
StorageClassName: &scName,
|
|
AccessModes: []apiv1.PersistentVolumeAccessMode{
|
|
apiv1.ReadWriteOnce,
|
|
},
|
|
Resources: apiv1.ResourceRequirements{
|
|
Requests: apiv1.ResourceList{
|
|
apiv1.ResourceName(apiv1.ResourceStorage): resource.MustParse(claimSize),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
_, err = c.client.CoreV1().PersistentVolumeClaims(namespaceID).Create(persistentVolumeClaim)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) CreateVolumeSnapshot(namespaceID, persistentVolumeClaimID, capacity string) error {
|
|
UID := strings.Split(uuid.New(), "-")
|
|
volumeSnapshot := &snapshotv1.VolumeSnapshot{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "snapshot-" + time.Now().Format("20060102150405") + "-" + UID[1],
|
|
Namespace: namespaceID,
|
|
Annotations: map[string]string{
|
|
"capacity": capacity,
|
|
},
|
|
},
|
|
Spec: snapshotv1.VolumeSnapshotSpec{
|
|
PersistentVolumeClaimName: persistentVolumeClaimID,
|
|
},
|
|
}
|
|
_, err := c.snapshotClient.VolumesnapshotV1().VolumeSnapshots(namespaceID).Create(volumeSnapshot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) GetLogs(namespaceID, podID string, containerNames []string) (io.ReadCloser, error) {
|
|
readClosersWithLabel := map[io.ReadCloser]string{}
|
|
for _, container := range containerNames {
|
|
req := c.client.CoreV1().Pods(namespaceID).GetLogs(
|
|
podID,
|
|
&apiv1.PodLogOptions{
|
|
Follow: true,
|
|
Timestamps: true,
|
|
Container: container,
|
|
},
|
|
)
|
|
readCloser, err := req.Stream()
|
|
if err != nil {
|
|
for rc := range readClosersWithLabel {
|
|
rc.Close()
|
|
}
|
|
return nil, err
|
|
}
|
|
readClosersWithLabel[readCloser] = container
|
|
}
|
|
|
|
return NewLogReadCloser(readClosersWithLabel), nil
|
|
}
|
|
|
|
func (c *client) Describe(namespaceID, resourceID string, groupKind schema.GroupKind, restMapping apimeta.RESTMapping) (io.ReadCloser, error) {
|
|
readClosersWithLabel := map[io.ReadCloser]string{}
|
|
restConfig, err := rest.InClusterConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
describer, ok := kubectl.DescriberFor(groupKind, restConfig)
|
|
if !ok {
|
|
describer, ok = kubectl.GenericDescriberFor(&restMapping, restConfig)
|
|
if !ok {
|
|
return nil, errors.New("Resource not found")
|
|
}
|
|
}
|
|
describerSetting := kubectldescribe.DescriberSettings{
|
|
ShowEvents: true,
|
|
}
|
|
obj, err := describer.Describe(namespaceID, resourceID, describerSetting)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
formattedObj := ioutil.NopCloser(bytes.NewReader([]byte(obj)))
|
|
readClosersWithLabel[formattedObj] = "describe"
|
|
|
|
return NewLogReadCloser(readClosersWithLabel), nil
|
|
}
|
|
|
|
func (c *client) DeletePod(namespaceID, podID string) error {
|
|
return c.client.CoreV1().Pods(namespaceID).Delete(podID, &metav1.DeleteOptions{})
|
|
}
|
|
|
|
func (c *client) DeleteVolumeSnapshot(namespaceID, volumeSnapshotID string) error {
|
|
return c.snapshotClient.VolumesnapshotV1().VolumeSnapshots(namespaceID).Delete(volumeSnapshotID, &metav1.DeleteOptions{})
|
|
}
|
|
|
|
func (c *client) ScaleUp(namespaceID, id string) error {
|
|
return c.modifyScale(namespaceID, id, func(scale *autoscalingv1.Scale) {
|
|
scale.Spec.Replicas++
|
|
})
|
|
}
|
|
|
|
func (c *client) ScaleDown(namespaceID, id string) error {
|
|
return c.modifyScale(namespaceID, id, func(scale *autoscalingv1.Scale) {
|
|
scale.Spec.Replicas--
|
|
})
|
|
}
|
|
|
|
func (c *client) modifyScale(namespaceID, id string, f func(*autoscalingv1.Scale)) error {
|
|
scaler := c.client.AppsV1().Deployments(namespaceID)
|
|
scale, err := scaler.GetScale(id, metav1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f(scale)
|
|
_, err = scaler.UpdateScale(id, scale)
|
|
return err
|
|
}
|
|
|
|
func (c *client) Stop() {
|
|
close(c.quit)
|
|
}
|