Skip to content

Commit dc16bee

Browse files
Merge pull request #10 from Dalee/selectors-and-commands
Execute command
2 parents e034ea1 + a7a80b4 commit dc16bee

9 files changed

Lines changed: 179 additions & 9 deletions

File tree

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,28 @@ to perform actual cleanup of deleted images!
164164
[14:37:42][Step 2/5] Process exited with code 0
165165
```
166166

167+
## Command execution
168+
169+
For each given selector, find all associated pods and execute provided command
170+
in each container of each pod.
171+
172+
### Sample output
173+
174+
```
175+
$ fuse exec --deployments app=acme-staging-wrk,app=acme-staging-adm --command date
176+
===> kubectl --namespace=default get deployment --selector=app=acme-staging-wrk -o yaml
177+
===> kubectl --namespace=default get deployment --selector=app=acme-staging-adm -o yaml
178+
===> kubectl --namespace=default get pods --selector=app=acme-staging-wrk,project=acme-188825 -o yaml
179+
===> kubectl --namespace=default get pods --selector=app=acme-staging-adm,project=acme-188825 -o yaml
180+
===> kubectl --namespace=default exec acme-staging-wrk-2842908200-wwlxe --container=acme-staging-wrk date
181+
===> Pod: default/acme-staging-wrk-2842908200-wwlxe, Container: acme-staging-wrk:
182+
Tue Jul 4 18:49:11 MSK 2017
183+
184+
===> kubectl --namespace=default exec acme-staging-adm-3301714455-wwnu8 --container=acme-staging-adm date
185+
===> Pod: default/acme-staging-adm-3301714455-wwnu8, Container: acme-staging-adm:
186+
Tue Jul 4 18:49:11 MSK 2017
187+
```
188+
167189
## Stability
168190

169191
Tool currently in pre-release stage, but, it is using heavily to deliver

bin/cmd/apply.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func init() {
3232
applyCmd.MarkFlagRequired("configuration")
3333
applyCmd.MarkFlagFilename("configuration", "yml", "yaml")
3434

35-
applyCmd.Flags().DurationVarP(&clusterTimeout, "rollout-timeout", "t", 2*time.Minute, "Rollout timeout")
35+
applyCmd.Flags().DurationVarP(&clusterTimeout, "rollout-timeout", "t", 3*time.Minute, "Rollout timeout")
3636
RootCmd.AddCommand(applyCmd)
3737
}
3838

@@ -153,7 +153,7 @@ func finalizeRollOut(specList *[]kubectl.Deployment, isRolledOut bool) error {
153153

154154
for _, d := range *rolledList {
155155
// get list of pods connected to deployment
156-
rlist, _ := kubectl.CommandPodListBySelector(d.GetNamespace(), d.GetSelector()).RunAndParse()
156+
rlist, _ := kubectl.CommandPodListBySelector(d.GetNamespace(), d.GetPodSelector()).RunAndParse()
157157
if rlist == nil {
158158
continue
159159
}
@@ -185,7 +185,7 @@ func finalizeRollOut(specList *[]kubectl.Deployment, isRolledOut bool) error {
185185
fmt.Println("==> Rollout failed, starting undo process...")
186186
for _, d := range *rolledList {
187187
// get list of replica sets connected to deployment
188-
rlist, err := kubectl.CommandReplicaSetListBySelector(d.GetNamespace(), d.GetSelector()).RunAndParse()
188+
rlist, err := kubectl.CommandReplicaSetListBySelector(d.GetNamespace(), d.GetPodSelector()).RunAndParse()
189189
if err != nil {
190190
return err
191191
}

bin/cmd/exec.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"fuse/pkg/kubectl"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var (
11+
// command itself
12+
execCmd = &cobra.Command{
13+
Use: "exec",
14+
Short: "Execute command in pods by selector",
15+
Long: ``,
16+
RunE: execCmdHandler,
17+
}
18+
19+
// Flags
20+
execCommand = ""
21+
deploymentSelectors = make([]string, 0)
22+
)
23+
24+
// register all flags
25+
func init() {
26+
execCmd.Flags().StringSliceVar(&deploymentSelectors, "deployments", []string{}, "Deployment selector (e.g. app=myapp)")
27+
execCmd.Flags().StringVar(&execCommand, "command", "", "Command to execute")
28+
RootCmd.AddCommand(execCmd)
29+
}
30+
31+
// command handler
32+
func execCmdHandler(cmd *cobra.Command, args []string) error {
33+
if execCommand == "" {
34+
return errors.New("No command provided")
35+
}
36+
37+
if len(deploymentSelectors) == 0 {
38+
// get deployment by selector
39+
return errors.New("No deployment selectors provided")
40+
}
41+
42+
// get deployment list by selector
43+
deploymentList := make([]kubectl.Deployment, 0)
44+
for _, s := range deploymentSelectors {
45+
resourceList, err := kubectl.CommandDeploymentListBySelector(namespaceFlag, []string{s}).RunAndParse()
46+
if err != nil {
47+
return err
48+
}
49+
50+
dl := resourceList.ToDeploymentList()
51+
deploymentList = append(deploymentList, dl...)
52+
}
53+
54+
podList := make([]kubectl.Pod, 0)
55+
56+
// for each deployment, find all pods
57+
for _, d := range deploymentList {
58+
podResourceList, err := kubectl.CommandPodListBySelector(namespaceFlag, d.GetPodSelector()).RunAndParse()
59+
if err != nil {
60+
return err
61+
}
62+
63+
pl := podResourceList.ToPodList()
64+
podList = append(podList, pl...)
65+
}
66+
67+
// for each container in pod, exec provided command
68+
for _, pod := range podList {
69+
for _, c := range pod.Spec.Containers {
70+
podName := pod.GetName()
71+
72+
stdout, err := kubectl.CommandExec(namespaceFlag, podName, c.Name, execCommand).RunPlain()
73+
fmt.Printf("===> Pod: %s, Container: %s:\n", pod.GetKey(), c.Name)
74+
fmt.Println(string(stdout))
75+
76+
if err != nil {
77+
return err
78+
}
79+
}
80+
}
81+
82+
return nil
83+
}

bin/cmd/garbage-collect.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ var (
2525
dryRunFlag = false
2626
ignoreMissingFlag = false
2727
registryURLFlag = ""
28-
namespaceFlag = ""
2928
ignoreTags = make([]string, 0)
3029

3130
// Docker Distribution client
@@ -34,8 +33,7 @@ var (
3433

3534
// register all flags
3635
func init() {
37-
garbageCollectCmd.Flags().StringVarP(&namespaceFlag, "namespace", "n", "default", "Kubernetes namespace to use")
38-
garbageCollectCmd.Flags().BoolVarP(&dryRunFlag, "dry-run", "d", false, "Do not execute destructive actions (default \"false\")")
36+
garbageCollectCmd.Flags().BoolVar(&dryRunFlag, "dry-run", false, "Do not execute destructive actions (default \"false\")")
3937
garbageCollectCmd.Flags().StringVarP(&registryURLFlag, "registry-url", "r", "", "Registry URL (e.g. \"https://registry.example.com:5000/\")")
4038
garbageCollectCmd.Flags().BoolVarP(&ignoreMissingFlag, "ignore-missing", "i", false, "Skip missing images in Registry (default \"false\")")
4139
garbageCollectCmd.Flags().StringSliceVarP(&ignoreTags, "keep-tag", "k", []string{}, "Keep tag in Registry, even if it not deployed (default none)")

bin/cmd/root.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ var (
2121
}
2222

2323
// global flag for current cluster context
24-
contextFlag = ""
24+
contextFlag = ""
25+
namespaceFlag = ""
2526
)
2627

2728
func init() {
2829
RootCmd.PersistentFlags().StringVarP(&contextFlag, "context", "c", "", "Override CLUSTER_CONTEXT defined in environment (default \"\")")
30+
execCmd.PersistentFlags().StringVarP(&namespaceFlag, "namespace", "n", "default", "Kubernetes namespace to use")
2931
}

pkg/kubectl/api.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,23 @@ func CommandRollback(namespace, kind, name string) *KubeCall {
8686
}
8787
}
8888

89+
// CommandExec execute command in container of pod
90+
func CommandExec(namespace, pod, container, command string) *KubeCall {
91+
p := newParser()
92+
c := newCommand([]string{
93+
fmt.Sprintf("--namespace=%s", formatNamespace(namespace)),
94+
"exec",
95+
fmt.Sprintf("%s", pod),
96+
fmt.Sprintf("--container=%s", container),
97+
command,
98+
})
99+
100+
return &KubeCall{
101+
Cmd: c,
102+
Parser: p,
103+
}
104+
}
105+
89106
// CommandNamespaceList return call which will return list of namespaces registered in kubernetes cluster
90107
func CommandNamespaceList() *KubeCall {
91108
p := newParser()
@@ -172,6 +189,25 @@ func CommandDeploymentList(namespace string) *KubeCall {
172189
}
173190
}
174191

192+
// CommandDeploymentListBySelector fetch deployments by provided selector
193+
func CommandDeploymentListBySelector(namespace string, selector []string) *KubeCall {
194+
selectorList := strings.Join(selector, ",")
195+
p := newParser()
196+
c := newCommand([]string{
197+
fmt.Sprintf("--namespace=%s", formatNamespace(namespace)),
198+
"get",
199+
"deployment",
200+
fmt.Sprintf("--selector=%s", selectorList),
201+
"-o",
202+
"yaml",
203+
})
204+
205+
return &KubeCall{
206+
Cmd: c,
207+
Parser: p,
208+
}
209+
}
210+
175211
// CommandPodListBySelector return list of pods in namespace with selector
176212
func CommandPodListBySelector(namespace string, selector []string) *KubeCall {
177213
selectorList := strings.Join(selector, ",")

pkg/kubectl/parser_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ metadata:
5050
app: test-label
5151
spec:
5252
template:
53+
metadata:
54+
labels:
55+
project: "test"
5356
spec:
5457
containers:
5558
- image: example.com/image:1
@@ -73,6 +76,7 @@ spec:
7376

7477
assert.Len(t, d.Metadata.Labels, 1)
7578
assert.Equal(t, d.Metadata.Labels["app"], "test-label")
79+
assert.Equal(t, []string{"project=test"}, d.GetPodSelector())
7680

7781
assert.Equal(t, d.Metadata.Generation, 42)
7882
}

pkg/kubectl/types.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,20 @@ type (
5252

5353
resourceContainer struct {
5454
Image string `yaml:"image"` // example.com:80/dalee/image:34
55+
Name string `yaml:"name"`
5556
}
5657

5758
resourceContainerSpec struct {
5859
Containers []resourceContainer `yaml:"containers"`
5960
}
6061

62+
resourceMetadataSpec struct {
63+
Labels map[string]string `yaml:"labels"`
64+
}
65+
6166
resourceTemplate struct {
62-
Spec resourceContainerSpec `yaml:"spec"`
67+
Metadata resourceMetadataSpec `yaml:"metadata"`
68+
Spec resourceContainerSpec `yaml:"spec"`
6369
}
6470

6571
resourceStrategyRolling struct {
@@ -283,6 +289,16 @@ func (d *Deployment) GetSelector() []string {
283289
return selectorList
284290
}
285291

292+
// GetPodSelector return slice of selectors associated with Deployment Spec template
293+
func (d *Deployment) GetPodSelector() []string {
294+
selectorList := make([]string, 0)
295+
for key, value := range d.Spec.Template.Metadata.Labels {
296+
selectorList = append(selectorList, fmt.Sprintf("%s=%s", key, value))
297+
}
298+
299+
return selectorList
300+
}
301+
286302
// ToDeployment interface method
287303
func (d *Deployment) ToDeployment() (*Deployment, error) {
288304
return d, nil

pkg/kubectl/types_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ func TestDeployment_Type(t *testing.T) {
6262
},
6363
Spec: resourceSpec{
6464
Replicas: 1,
65+
Template: resourceTemplate{
66+
Metadata: resourceMetadataSpec{
67+
Labels: map[string]string{
68+
"project": "test-project-2",
69+
"project_build": "12",
70+
},
71+
},
72+
},
6573
Strategy: resourceStrategy{
6674
Type: strategyTypeRollingUpdate,
6775
RollingUpdate: resourceStrategyRolling{
@@ -79,7 +87,7 @@ func TestDeployment_Type(t *testing.T) {
7987
assert.Equal(t, "test-deployment", d.GetName())
8088
assert.Equal(t, "3eb259fc-bc6f-11e6-a342-005056ba5444", d.GetUUID())
8189
assert.Equal(t, "default/test-deployment", d.GetKey())
82-
assert.Equal(t, []string{"sample-label1=example1"}, d.GetSelector())
90+
assert.Equal(t, []string{"project=test-project-2", "project_build=12"}, d.GetPodSelector())
8391
assert.False(t, d.IsReady())
8492
assert.Equal(t, "Ready: false, Generation: meta=12 observed=0, Replicas: s=1, u=0, a=0, na=1", d.GetStatusString())
8593

@@ -188,6 +196,7 @@ func TestDeployment_IsReady(t *testing.T) {
188196
// initial check
189197
assert.False(t, d.IsReady())
190198
assert.Equal(t, "Ready: false, Generation: meta=12 observed=11, Replicas: s=1, u=0, a=0, na=0", d.GetStatusString())
199+
assert.Empty(t, d.GetPodSelector()) // since no labels defined in Spec.Metadata
191200

192201
// 1) k8s started rollout
193202
d.Status.ObservedGeneration = 12

0 commit comments

Comments
 (0)