Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions cmd/kill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package cmd

import (
"errors"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

// killCmd represents the kill command
var killCmd = &cobra.Command{
Use: "kill",
Short: "kill a pod",
Long: `This command find the corresponding pod and kill it`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
errs := runKill(namespace, args)
if len(errs) > 0 {
for _, err := range errs {
logrus.Error(err.Error())
}
logrus.Fatal("some pod could not be killed")
}
},
}

func NewKillCommand() *cobra.Command {
addCommonNamespaceCommandFlags(killCmd)

return killCmd
}

func runKill(namespace string, deployments []string) []error {
if namespace == "" {
return []error{errors.New("you must specified a namespace using the --namespace flag")}
}

api := newAPI(newFileClient(dir), newKubernetesClient())

errs := api.Kill(namespace, deployments)
if len(errs) > 0 {
return errs
}

logrus.WithFields(logrus.Fields{
"namespace": namespace,
"deployments": deployments,
}).Info("pod are being killed")

return nil
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func NewBlackbeardCommand() *cobra.Command {
rootCmd.AddCommand(NewGetCommand())
rootCmd.AddCommand(NewResetCommand())
rootCmd.AddCommand(NewVersionCommand())
rootCmd.AddCommand(NewKillCommand())

rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.blackbeard.yaml)")
rootCmd.PersistentFlags().StringVar(&dir, "dir", "", "Use the specified dir as root path to execute commands. Default is the current dir.")
Expand Down
23 changes: 1 addition & 22 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package api

import (
"strings"
"time"

"github.com/Meetic/blackbeard/pkg/playbook"
"github.com/Meetic/blackbeard/pkg/resource"
"github.com/Meetic/blackbeard/pkg/version"
"github.com/sirupsen/logrus"
)

Expand All @@ -19,6 +17,7 @@ type Api interface {
Pods() resource.PodService
Create(namespace string) (playbook.Inventory, error)
Delete(namespace string, wait bool) error
Kill(namespace string, deployments []string) []error
ListExposedServices(namespace string) ([]resource.Service, error)
ListNamespaces() ([]Namespace, error)
Reset(namespace string, configPath string) error
Expand Down Expand Up @@ -211,23 +210,3 @@ func (api *api) WatchDelete() {
}
}
}

type Version struct {
Blackbeard string `json:"blackbeard"`
Kubernetes string `json:"kubernetes"`
Kubectl string `json:"kubectl"`
}

func (api *api) GetVersion() (*Version, error) {
v, err := api.cluster.GetVersion()

if err != nil {
return nil, err
}

return &Version{
Blackbeard: version.GetVersion(),
Kubernetes: strings.Join([]string{v.ClientVersion.Major, v.ClientVersion.Minor}, "."),
Kubectl: strings.Join([]string{v.ServerVersion.Major, v.ServerVersion.Minor}, "."),
}, nil
}
6 changes: 3 additions & 3 deletions pkg/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var (
mock.NewConfigRepository(),
mock.NewPlaybookRepository(),
mock.NewNamespaceRepository(kube, false),
kubernetes.NewPodRepository(kube),
mock.NewPodRepository(kube),
kubernetes.NewServiceRepository(kube, "kube.test"),
kubernetes.NewClusterRepository(),
)
Expand All @@ -42,7 +42,7 @@ func (clusterRepositoryMock) GetVersion() (*resource.Version, error) {
}

func TestGetVersion(t *testing.T) {
blackbeard = api.NewApi(
b := api.NewApi(
mock.NewInventoryRepository(),
mock.NewConfigRepository(),
mock.NewPlaybookRepository(),
Expand All @@ -52,7 +52,7 @@ func TestGetVersion(t *testing.T) {
&clusterRepositoryMock{},
)

version, err := blackbeard.GetVersion()
version, err := b.GetVersion()

assert.Nil(t, err)
assert.Equal(t, version, &api.Version{Blackbeard: "dev", Kubernetes: "1.2", Kubectl: "0.9"})
Expand Down
25 changes: 25 additions & 0 deletions pkg/api/deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package api

func (api *api) Kill(namespace string, deployments []string) []error {

var errors []error

for _, d := range deployments {
pod, err := api.Pods().Find(namespace, d)
if err != nil {
errors = append(errors, err)
continue
}

if err := api.Pods().Delete(namespace, pod); err != nil {
errors = append(errors, err)
continue
}
}

if len(errors) > 0 {
return errors
}

return nil
}
17 changes: 17 additions & 0 deletions pkg/api/deployment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package api_test

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestKill(t *testing.T) {
errs := blackbeard.Kill("foo", []string{"test"})
assert.Empty(t, errs)
}

func TestKillKO(t *testing.T) {
errs := blackbeard.Kill("foo", []string{"bar"})
assert.NotEmpty(t, errs)
}
27 changes: 27 additions & 0 deletions pkg/api/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package api

import (
"strings"

"github.com/Meetic/blackbeard/pkg/version"
)

type Version struct {
Blackbeard string `json:"blackbeard"`
Kubernetes string `json:"kubernetes"`
Kubectl string `json:"kubectl"`
}

func (api *api) GetVersion() (*Version, error) {
v, err := api.cluster.GetVersion()

if err != nil {
return nil, err
}

return &Version{
Blackbeard: version.GetVersion(),
Kubernetes: strings.Join([]string{v.ClientVersion.Major, v.ClientVersion.Minor}, "."),
Kubectl: strings.Join([]string{v.ServerVersion.Major, v.ServerVersion.Minor}, "."),
}, nil
}
23 changes: 23 additions & 0 deletions pkg/http/resource_handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package http

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -80,3 +81,25 @@ func (h *Handler) GetStatuses(c *gin.Context) {

c.JSON(http.StatusOK, statuses)
}

type killQuery []string

// Kill handle the kill deployments
func (h *Handler) Kill(c *gin.Context) {

var kQuery killQuery

if err := c.BindJSON(&kQuery); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

// Kill the corresponding pods
errs := h.api.Kill(c.Params.ByName("namespace"), kQuery)

if len(errs) > 0 {
c.JSON(http.StatusBadRequest, gin.H{"errors": fmt.Sprintf("%v", errs)})
return
}
c.Status(http.StatusOK)
}
1 change: 1 addition & 0 deletions pkg/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func NewHandler(api api.Api, websocket WsHandler, configPath string, corsEnable
h.engine.GET("/defaults", h.GetDefaults)
h.engine.PUT("/inventories/:namespace", h.Update)
h.engine.DELETE("/inventories/:namespace", h.Delete)
h.engine.DELETE("/inventories/:namespace/deployments", h.Kill)
h.engine.GET("/ws", func(c *gin.Context) {
websocket.Handle(c.Writer, c.Request)
})
Expand Down
8 changes: 4 additions & 4 deletions pkg/kubernetes/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,16 @@ func execute(c string, t time.Duration) error {
if t > 0 {
timer = time.NewTimer(t)
var err error
go func(timer *time.Timer, cmd *exec.Cmd) {
go func(timer *time.Timer, cmd *exec.Cmd, err *error) {
for range timer.C {
e := cmd.Process.Kill()
if e != nil {
err = errors.New("the command has timeout but the process could not be killed")
*err = errors.New("the command has timeout but the process could not be killed")
} else {
err = errors.New("the command timed out")
*err = errors.New("the command timed out")
}
}
}(timer, cmd)
}(timer, cmd, &err)
}

err = cmd.Wait()
Expand Down
4 changes: 4 additions & 0 deletions pkg/kubernetes/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ func (pr *podRepository) List(n string) (resource.Pods, error) {

return pods, nil
}

func (pr *podRepository) Delete(n string, pod resource.Pod) error {
return pr.kubernetes.CoreV1().Pods(n).Delete(pod.Name, &metav1.DeleteOptions{})
}
12 changes: 10 additions & 2 deletions pkg/mock/pod.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package mock

import (
"errors"

"github.com/Meetic/blackbeard/pkg/resource"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
)

Expand All @@ -21,7 +23,6 @@ func NewPodRepository(kubernetes kubernetes.Interface) resource.PodRepository {
// GetPods of all the pods in a given namespace.
// This method returns a Pods slice containing the pod name and the pod status (pod status phase).
func (rs *podRepository) List(n string) (resource.Pods, error) {

if n == "testko" {

pods := resource.Pods{
Expand All @@ -44,3 +45,10 @@ func (rs *podRepository) List(n string) (resource.Pods, error) {

return pods, nil
}

func (rs *podRepository) Delete(n string, p resource.Pod) error {
if p.Name == "err" {
return errors.New("an error occurred in pod deletion")
}
return nil
}
32 changes: 29 additions & 3 deletions pkg/resource/pod.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package resource

import (
"k8s.io/api/core/v1"
"fmt"
"strings"

v1 "k8s.io/api/core/v1"
)

// Pods represent a list of pods.
Expand All @@ -23,12 +26,15 @@ type podService struct {

// PodRepository represents the way Pods are managed
type PodRepository interface {
List(string) (Pods, error)
List(namespace string) (Pods, error)
Delete(namespace string, pod Pod) error
//Get(string, string) (Pod, error)
}

type PodService interface {
List(string) (Pods, error)
List(namespace string) (Pods, error)
Find(namespace, deployment string) (Pod, error)
Delete(namespace string, pod Pod) error
}

//NewPodService returns a new PodService
Expand All @@ -42,3 +48,23 @@ func NewPodService(pods PodRepository) PodService {
func (ps *podService) List(namespace string) (Pods, error) {
return ps.pods.List(namespace)
}

func (ps *podService) Find(namespace, deployment string) (Pod, error) {
podList, err := ps.pods.List(namespace)

if err != nil {
return Pod{}, err
}

for _, pod := range podList {
if strings.Contains(pod.Name, deployment) {
return pod, nil
}
}

return Pod{}, fmt.Errorf("no pod have been found for deployment name : %s", deployment)
}

func (ps *podService) Delete(namespace string, pod Pod) error {
return ps.pods.Delete(namespace, pod)
}
32 changes: 32 additions & 0 deletions pkg/resource/pod_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package resource_test

import (
"testing"

"github.com/Meetic/blackbeard/pkg/mock"
"github.com/Meetic/blackbeard/pkg/resource"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
)

var (
pods = resource.NewPodService(mock.NewPodRepository(kube))
)

func TestFindOk(t *testing.T) {
p, err := pods.Find("foo", "te")

assert.Contains(t, p.Name, "te")
assert.Nil(t, err)
}

func TestFindNOk(t *testing.T) {
_, err := pods.Find("foo", "bar")

assert.NotNil(t, err)
}

func TestDeleteNOk(t *testing.T) {
err := pods.Delete("foo", resource.Pod{"err", v1.PodRunning})
assert.NotNil(t, err)
}
Loading