1
1
package commands
2
2
3
3
import (
4
+ "bytes"
4
5
"fmt"
5
6
"io"
6
7
"sort"
7
8
"strings"
8
9
"text/tabwriter"
9
10
"time"
10
11
12
+ "github.com/deislabs/cnab-go/action"
13
+ "github.com/docker/app/internal"
14
+ "github.com/docker/app/internal/cliopts"
15
+ "github.com/docker/app/internal/cnab"
11
16
"github.com/docker/app/internal/store"
12
17
"github.com/docker/cli/cli"
13
18
"github.com/docker/cli/cli/command"
@@ -22,55 +27,79 @@ import (
22
27
var (
23
28
listColumns = []struct {
24
29
header string
25
- value func (i * store. Installation ) string
30
+ value func (i Installation ) string
26
31
}{
27
- {"RUNNING APP" , func (i * store.Installation ) string { return i .Name }},
28
- {"APP NAME" , func (i * store.Installation ) string { return fmt .Sprintf ("%s (%s)" , i .Bundle .Name , i .Bundle .Version ) }},
29
- {"LAST ACTION" , func (i * store.Installation ) string { return i .Result .Action }},
30
- {"RESULT" , func (i * store.Installation ) string { return i .Result .Status }},
31
- {"CREATED" , func (i * store.Installation ) string {
32
+ {"RUNNING APP" , func (i Installation ) string { return i .Name }},
33
+ {"APP NAME" , func (i Installation ) string { return fmt .Sprintf ("%s (%s)" , i .Bundle .Name , i .Bundle .Version ) }},
34
+ {"SERVICES" , printServices },
35
+ {"LAST ACTION" , func (i Installation ) string { return i .Result .Action }},
36
+ {"RESULT" , func (i Installation ) string { return i .Result .Status }},
37
+ {"CREATED" , func (i Installation ) string {
32
38
return fmt .Sprintf ("%s ago" , units .HumanDuration (time .Since (i .Created )))
33
39
}},
34
- {"MODIFIED" , func (i * store. Installation ) string {
40
+ {"MODIFIED" , func (i Installation ) string {
35
41
return fmt .Sprintf ("%s ago" , units .HumanDuration (time .Since (i .Modified )))
36
42
}},
37
- {"REFERENCE" , func (i * store. Installation ) string { return i .Reference }},
43
+ {"REFERENCE" , func (i Installation ) string { return i .Reference }},
38
44
}
39
45
)
40
46
41
- func listCmd (dockerCli command.Cli ) * cobra.Command {
42
- var template string
47
+ type listOptions struct {
48
+ template string
49
+ }
50
+
51
+ func listCmd (dockerCli command.Cli , installerContext * cliopts.InstallerContextOptions ) * cobra.Command {
52
+ var opts listOptions
43
53
cmd := & cobra.Command {
44
54
Use : "ls [OPTIONS]" ,
45
55
Short : "List running Apps" ,
46
56
Aliases : []string {"list" },
47
57
Args : cli .NoArgs ,
48
58
RunE : func (cmd * cobra.Command , args []string ) error {
49
- return runList (dockerCli , template )
59
+ return runList (dockerCli , opts , installerContext )
50
60
},
51
61
}
52
62
53
- cmd .Flags ().StringVarP (& template , "format" , "f" , "" , "Format the output using the given syntax or Go template" )
63
+ cmd .Flags ().StringVarP (& opts . template , "format" , "f" , "" , "Format the output using the given syntax or Go template" )
54
64
cmd .Flags ().SetAnnotation ("format" , "experimentalCLI" , []string {"true" }) //nolint:errcheck
55
65
return cmd
56
66
}
57
67
58
- func runList (dockerCli command.Cli , template string ) error {
59
- installations , err := getInstallations (dockerCli .CurrentContext (), config .Dir ())
68
+ func runList (dockerCli command.Cli , opts listOptions , installerContext * cliopts.InstallerContextOptions ) error {
69
+ // initialize stores
70
+ appstore , err := store .NewApplicationStore (config .Dir ())
71
+ if err != nil {
72
+ return err
73
+ }
74
+ targetContext := dockerCli .CurrentContext ()
75
+ installationStore , err := appstore .InstallationStore (targetContext )
76
+ if err != nil {
77
+ return err
78
+ }
79
+
80
+ fetcher := & serviceFetcher {
81
+ dockerCli : dockerCli ,
82
+ opts : opts ,
83
+ installerContext : installerContext ,
84
+ }
85
+ installations , err := getInstallations (installationStore , fetcher )
86
+ if installations == nil && err != nil {
87
+ return err
88
+ }
60
89
if err != nil {
61
90
return err
62
91
}
63
92
64
- if template == "json" {
93
+ if opts . template == "json" {
65
94
bytes , err := json .MarshalIndent (installations , "" , " " )
66
95
if err != nil {
67
96
return errors .Errorf ("Failed to marshall json: %s" , err )
68
97
}
69
98
_ , err = dockerCli .Out ().Write (bytes )
70
99
return err
71
100
}
72
- if template != "" {
73
- tmpl , err := templates .Parse (template )
101
+ if opts . template != "" {
102
+ tmpl , err := templates .Parse (opts . template )
74
103
if err != nil {
75
104
return errors .Errorf ("Template parsing error: %s" , err )
76
105
}
@@ -94,38 +123,128 @@ func printHeaders(w io.Writer) {
94
123
fmt .Fprintln (w , strings .Join (headers , "\t " ))
95
124
}
96
125
97
- func printValues (w io.Writer , installation * store. Installation ) {
126
+ func printValues (w io.Writer , installation Installation ) {
98
127
var values []string
99
128
for _ , column := range listColumns {
100
129
values = append (values , column .value (installation ))
101
130
}
102
131
fmt .Fprintln (w , strings .Join (values , "\t " ))
103
132
}
104
133
105
- func getInstallations (targetContext , configDir string ) ([]* store.Installation , error ) {
106
- appstore , err := store .NewApplicationStore (configDir )
107
- if err != nil {
108
- return nil , err
109
- }
110
- installationStore , err := appstore .InstallationStore (targetContext )
111
- if err != nil {
112
- return nil , err
113
- }
134
+ type Installation struct {
135
+ * store.Installation
136
+ Services appServices `json:",omitempty"`
137
+ }
138
+
139
+ func getInstallations (installationStore store.InstallationStore , fetcher ServiceFetcher ) ([]Installation , error ) {
114
140
installationNames , err := installationStore .List ()
115
141
if err != nil {
116
142
return nil , err
117
143
}
118
- installations := make ([]* store. Installation , len (installationNames ))
144
+ installations := make ([]Installation , len (installationNames ))
119
145
for i , name := range installationNames {
120
146
installation , err := installationStore .Read (name )
121
147
if err != nil {
122
148
return nil , err
123
149
}
124
- installations [i ] = installation
150
+ services , err := fetcher .getServices (installation )
151
+ if err != nil {
152
+ return nil , err
153
+ }
154
+ installations [i ] = Installation {Installation : installation , Services : services }
125
155
}
126
156
// Sort installations with last modified first
127
157
sort .Slice (installations , func (i , j int ) bool {
128
158
return installations [i ].Modified .After (installations [j ].Modified )
129
159
})
160
+
130
161
return installations , nil
131
162
}
163
+
164
+ type ServiceStatus struct {
165
+ DesiredTasks int
166
+ RunningTasks int
167
+ }
168
+
169
+ type appServices map [string ]ServiceStatus
170
+
171
+ type runningService struct {
172
+ Spec struct {
173
+ Name string
174
+ }
175
+ ServiceStatus ServiceStatus
176
+ }
177
+
178
+ type serviceFetcher struct {
179
+ dockerCli command.Cli
180
+ opts listOptions
181
+ installerContext * cliopts.InstallerContextOptions
182
+ }
183
+
184
+ type ServiceFetcher interface {
185
+ getServices (* store.Installation ) (appServices , error )
186
+ }
187
+
188
+ func (s * serviceFetcher ) getServices (installation * store.Installation ) (appServices , error ) {
189
+ defer muteDockerCli (s .dockerCli )()
190
+
191
+ // bundle without status action returns empty services
192
+ if ! hasAction (installation .Bundle , internal .ActionStatusJSONName ) {
193
+ return nil , nil
194
+ }
195
+ creds , err := prepareCredentialSet (installation .Bundle ,
196
+ addDockerCredentials (s .dockerCli .CurrentContext (), s .dockerCli .ContextStore ()),
197
+ addRegistryCredentials (false , s .dockerCli ),
198
+ )
199
+ if err != nil {
200
+ return nil , err
201
+ }
202
+
203
+ var buf bytes.Buffer
204
+ driverImpl , errBuf , err := cnab .SetupDriver (installation , s .dockerCli , s .installerContext , & buf )
205
+ if err != nil {
206
+ return nil , err
207
+ }
208
+ a := & action.RunCustom {
209
+ Driver : driverImpl ,
210
+ Action : internal .ActionStatusJSONName ,
211
+ }
212
+ // fetch output from status JSON action and parse it
213
+ if err := a .Run (& installation .Claim , creds ); err != nil {
214
+ return nil , fmt .Errorf ("failed to get app %q status : %s\n %s" , installation .Name , err , errBuf )
215
+ }
216
+ var runningServices []runningService
217
+ if err := json .Unmarshal (buf .Bytes (), & runningServices ); err != nil {
218
+ return nil , err
219
+ }
220
+
221
+ services := make (appServices , len (installation .Bundle .Images ))
222
+ for name := range installation .Bundle .Images {
223
+ services [name ] = getRunningService (runningServices , installation .Name , name )
224
+ }
225
+
226
+ return services , nil
227
+ }
228
+
229
+ func getRunningService (services []runningService , app , name string ) ServiceStatus {
230
+ for _ , s := range services {
231
+ // swarm services are prefixed by app name
232
+ if s .Spec .Name == name || s .Spec .Name == fmt .Sprintf ("%s_%s" , app , name ) {
233
+ return s .ServiceStatus
234
+ }
235
+ }
236
+ return ServiceStatus {}
237
+ }
238
+
239
+ func printServices (i Installation ) string {
240
+ if len (i .Services ) == 0 {
241
+ return "N/A"
242
+ }
243
+ var runningServices int
244
+ for _ , s := range i .Services {
245
+ if s .RunningTasks > 0 {
246
+ runningServices ++
247
+ }
248
+ }
249
+ return fmt .Sprintf ("%d/%d" , runningServices , len (i .Services ))
250
+ }
0 commit comments