|
4 | 4 | package cmd
|
5 | 5 |
|
6 | 6 | import (
|
7 |
| - "context" |
8 |
| - "fmt" |
9 |
| - "io/ioutil" |
10 |
| - "log" |
11 |
| - "os" |
12 |
| - "strconv" |
13 |
| - "strings" |
14 |
| - "time" |
15 |
| - |
16 |
| - "github.com/docker/docker/api/types" |
17 |
| - "github.com/docker/docker/api/types/filters" |
18 |
| - "github.com/docker/docker/api/types/mount" |
19 |
| - "github.com/docker/docker/api/types/swarm" |
20 |
| - "github.com/docker/docker/client" |
21 |
| - |
| 7 | + jswarm "github.com/alexellis/jaas/pkg/swarm" |
| 8 | + jtypes "github.com/alexellis/jaas/pkg/types" |
22 | 9 | "github.com/spf13/cobra"
|
23 | 10 | )
|
24 | 11 |
|
25 | 12 | var (
|
26 |
| - taskRequest TaskRequest |
| 13 | + taskRequest jtypes.TaskRequest |
27 | 14 | verbose bool
|
28 | 15 | )
|
29 | 16 |
|
@@ -57,317 +44,6 @@ var runCmd = &cobra.Command{
|
57 | 44 | }
|
58 | 45 |
|
59 | 46 | func runRun(cmd *cobra.Command, args []string) error {
|
60 |
| - err := runTask(taskRequest) |
| 47 | + err := jswarm.RunTask(taskRequest) |
61 | 48 | return err
|
62 | 49 | }
|
63 |
| - |
64 |
| -func validate(taskRequest TaskRequest) error { |
65 |
| - if len(taskRequest.Image) == 0 { |
66 |
| - return fmt.Errorf("must a valid supply --image") |
67 |
| - } |
68 |
| - return nil |
69 |
| -} |
70 |
| - |
71 |
| -func runTask(taskRequest TaskRequest) error { |
72 |
| - if validationErr := validate(taskRequest); validationErr != nil { |
73 |
| - return validationErr |
74 |
| - } |
75 |
| - |
76 |
| - if verbose { |
77 |
| - fmt.Printf("Running.. OK %s\n", taskRequest.Image) |
78 |
| - fmt.Printf("Connected to.. OK %s\n", taskRequest.Networks) |
79 |
| - fmt.Printf("Constraints: %s\n", taskRequest.Constraints) |
80 |
| - fmt.Printf("envVars: %s\n", taskRequest.EnvVars) |
81 |
| - fmt.Printf("Secrets: %s\n", taskRequest.Secrets) |
82 |
| - } |
83 |
| - |
84 |
| - timeoutVal, parseErr := time.ParseDuration(taskRequest.Timeout) |
85 |
| - if parseErr != nil { |
86 |
| - return parseErr |
87 |
| - } |
88 |
| - |
89 |
| - if verbose { |
90 |
| - fmt.Printf("timeout: %s\n", timeoutVal) |
91 |
| - } |
92 |
| - |
93 |
| - var c *client.Client |
94 |
| - var err error |
95 |
| - c, err = client.NewEnvClient() |
96 |
| - if err != nil { |
97 |
| - |
98 |
| - return fmt.Errorf("is the Docker Daemon running? Error: %s", err.Error()) |
99 |
| - } |
100 |
| - |
101 |
| - // Check that experimental mode is enabled on the daemon, fall back to no logging if not |
102 |
| - versionInfo, versionErr := c.ServerVersion(context.Background()) |
103 |
| - if versionErr != nil { |
104 |
| - log.Fatal("Is the Docker Daemon running?") |
105 |
| - |
106 |
| - return versionErr |
107 |
| - } |
108 |
| - |
109 |
| - if taskRequest.ShowLogs { |
110 |
| - apiVersion, parseErr := strconv.ParseFloat(versionInfo.APIVersion, 64) |
111 |
| - if parseErr != nil { |
112 |
| - apiVersion = 0 |
113 |
| - } |
114 |
| - if apiVersion < 1.29 && versionInfo.Experimental == false { |
115 |
| - return fmt.Errorf("experimental daemon or Docker API Version 1.29+ required to display service logs, falling back to no log display") |
116 |
| - } |
117 |
| - } |
118 |
| - |
119 |
| - spec := makeSpec(taskRequest.Image, taskRequest.EnvVars) |
120 |
| - if len(taskRequest.Networks) > 0 { |
121 |
| - nets := []swarm.NetworkAttachmentConfig{ |
122 |
| - swarm.NetworkAttachmentConfig{Target: taskRequest.Networks[0]}, |
123 |
| - } |
124 |
| - spec.Networks = nets |
125 |
| - } |
126 |
| - |
127 |
| - createOptions := types.ServiceCreateOptions{} |
128 |
| - |
129 |
| - if len(taskRequest.RegistryAuth) > 0 { |
130 |
| - createOptions.EncodedRegistryAuth = taskRequest.RegistryAuth |
131 |
| - fmt.Println("Using RegistryAuth") |
132 |
| - } |
133 |
| - |
134 |
| - placement := &swarm.Placement{} |
135 |
| - if len(taskRequest.Constraints) > 0 { |
136 |
| - placement.Constraints = taskRequest.Constraints |
137 |
| - spec.TaskTemplate.Placement = placement |
138 |
| - } |
139 |
| - |
140 |
| - if len(taskRequest.Command) > 0 { |
141 |
| - spec.TaskTemplate.ContainerSpec.Command = strings.Split(taskRequest.Command, " ") |
142 |
| - } |
143 |
| - |
144 |
| - if len(taskRequest.EnvFiles) > 0 { |
145 |
| - for _, file := range taskRequest.EnvFiles { |
146 |
| - envs, err := readEnvs(file) |
147 |
| - if err != nil { |
148 |
| - fmt.Fprintf(os.Stderr, "%s", err) |
149 |
| - os.Exit(1) |
150 |
| - } |
151 |
| - |
152 |
| - for _, env := range envs { |
153 |
| - spec.TaskTemplate.ContainerSpec.Env = append(spec.TaskTemplate.ContainerSpec.Env, env) |
154 |
| - } |
155 |
| - } |
156 |
| - } |
157 |
| - |
158 |
| - spec.TaskTemplate.ContainerSpec.Mounts = []mount.Mount{} |
159 |
| - for _, bindMount := range taskRequest.Mounts { |
160 |
| - parts := strings.Split(bindMount, "=") |
161 |
| - if len(parts) < 2 || len(parts) > 2 { |
162 |
| - fmt.Fprintf(os.Stderr, "Bind-mounts must be specified as: src=dest, i.e. --mount /home/alex/tmp/=/tmp/\n") |
163 |
| - os.Exit(1) |
164 |
| - } |
165 |
| - |
166 |
| - if len(parts) == 2 { |
167 |
| - mountVal := mount.Mount{ |
168 |
| - Source: parts[0], |
169 |
| - Target: parts[1], |
170 |
| - } |
171 |
| - |
172 |
| - spec.TaskTemplate.ContainerSpec.Mounts = append(spec.TaskTemplate.ContainerSpec.Mounts, mountVal) |
173 |
| - } |
174 |
| - } |
175 |
| - |
176 |
| - secretList, err := c.SecretList(context.Background(), types.SecretListOptions{}) |
177 |
| - |
178 |
| - spec.TaskTemplate.ContainerSpec.Secrets = []*swarm.SecretReference{} |
179 |
| - for _, serviceSecret := range taskRequest.Secrets { |
180 |
| - var secretID string |
181 |
| - for _, s := range secretList { |
182 |
| - if serviceSecret == s.Spec.Annotations.Name { |
183 |
| - secretID = s.ID |
184 |
| - break |
185 |
| - } |
186 |
| - } |
187 |
| - if secretID == "" { |
188 |
| - fmt.Fprintf(os.Stderr, "No existing secret has name that matches %s\n", serviceSecret) |
189 |
| - os.Exit(1) |
190 |
| - } |
191 |
| - |
192 |
| - secretVal := swarm.SecretReference{ |
193 |
| - File: &swarm.SecretReferenceFileTarget{ |
194 |
| - Name: serviceSecret, |
195 |
| - UID: "0", |
196 |
| - GID: "0", |
197 |
| - Mode: os.FileMode(0444), // File can be read by any user inside the container |
198 |
| - }, |
199 |
| - SecretName: serviceSecret, |
200 |
| - SecretID: secretID, |
201 |
| - } |
202 |
| - |
203 |
| - spec.TaskTemplate.ContainerSpec.Secrets = append(spec.TaskTemplate.ContainerSpec.Secrets, &secretVal) |
204 |
| - } |
205 |
| - |
206 |
| - createResponse, _ := c.ServiceCreate(context.Background(), spec, createOptions) |
207 |
| - opts := types.ServiceInspectOptions{InsertDefaults: true} |
208 |
| - |
209 |
| - service, _, _ := c.ServiceInspectWithRaw(context.Background(), createResponse.ID, opts) |
210 |
| - fmt.Printf("Service created: %s (%s)\n", service.Spec.Name, createResponse.ID) |
211 |
| - |
212 |
| - taskExitCode := pollTask(c, createResponse.ID, timeoutVal, taskRequest.ShowLogs, taskRequest.RemoveService) |
213 |
| - os.Exit(taskExitCode) |
214 |
| - return nil |
215 |
| -} |
216 |
| - |
217 |
| -func makeSpec(image string, envVars []string) swarm.ServiceSpec { |
218 |
| - max := uint64(1) |
219 |
| - |
220 |
| - spec := swarm.ServiceSpec{ |
221 |
| - TaskTemplate: swarm.TaskSpec{ |
222 |
| - RestartPolicy: &swarm.RestartPolicy{ |
223 |
| - MaxAttempts: &max, |
224 |
| - Condition: swarm.RestartPolicyConditionNone, |
225 |
| - }, |
226 |
| - ContainerSpec: &swarm.ContainerSpec{ |
227 |
| - Image: image, |
228 |
| - Env: envVars, |
229 |
| - }, |
230 |
| - }, |
231 |
| - } |
232 |
| - return spec |
233 |
| -} |
234 |
| - |
235 |
| -func readEnvs(file string) ([]string, error) { |
236 |
| - var err error |
237 |
| - var envs []string |
238 |
| - |
239 |
| - data, readErr := ioutil.ReadFile(file) |
240 |
| - if readErr != nil { |
241 |
| - return envs, readErr |
242 |
| - } |
243 |
| - |
244 |
| - lines := strings.Split(string(data), "\n") |
245 |
| - for n, line := range lines { |
246 |
| - if len(line) > 0 { |
247 |
| - if strings.Index(line, "=") == -1 { |
248 |
| - err = fmt.Errorf("no seperator found in line %d of env-file %s", n, file) |
249 |
| - break |
250 |
| - } |
251 |
| - envs = append(envs, line) |
252 |
| - } |
253 |
| - } |
254 |
| - return envs, err |
255 |
| -} |
256 |
| - |
257 |
| -const swarmError = -999 |
258 |
| -const timeoutError = -998 |
259 |
| - |
260 |
| -func pollTask(c *client.Client, id string, timeout time.Duration, showlogs, removeService bool) int { |
261 |
| - svcFilters := filters.NewArgs() |
262 |
| - svcFilters.Add("id", id) |
263 |
| - |
264 |
| - exitCode := swarmError |
265 |
| - |
266 |
| - opts := types.ServiceListOptions{ |
267 |
| - Filters: svcFilters, |
268 |
| - } |
269 |
| - |
270 |
| - list, _ := c.ServiceList(context.Background(), opts) |
271 |
| - for _, item := range list { |
272 |
| - start := time.Now() |
273 |
| - end := start.Add(timeout) |
274 |
| - |
275 |
| - fmt.Println("ID: ", item.ID, " Update at: ", item.UpdatedAt) |
276 |
| - for { |
277 |
| - time.Sleep(500 * time.Millisecond) |
278 |
| - |
279 |
| - taskExitCode, found := showTasks(c, item.ID, showlogs, removeService) |
280 |
| - if found { |
281 |
| - exitCode = taskExitCode |
282 |
| - break |
283 |
| - } |
284 |
| - now := time.Now() |
285 |
| - if now.After(end) { |
286 |
| - fmt.Printf("Timing out after %s.", timeout.String()) |
287 |
| - return timeoutError |
288 |
| - } |
289 |
| - } |
290 |
| - } |
291 |
| - |
292 |
| - return exitCode |
293 |
| -} |
294 |
| - |
295 |
| -func showTasks(c *client.Client, id string, showLogs, removeService bool) (int, bool) { |
296 |
| - filters1 := filters.NewArgs() |
297 |
| - filters1.Add("service", id) |
298 |
| - |
299 |
| - tasks, _ := c.TaskList(context.Background(), types.TaskListOptions{ |
300 |
| - Filters: filters1, |
301 |
| - }) |
302 |
| - |
303 |
| - exitCode := 1 |
304 |
| - var done bool |
305 |
| - stopStates := []swarm.TaskState{ |
306 |
| - swarm.TaskStateComplete, |
307 |
| - swarm.TaskStateFailed, |
308 |
| - swarm.TaskStateRejected, |
309 |
| - } |
310 |
| - |
311 |
| - for _, task := range tasks { |
312 |
| - |
313 |
| - stop := false |
314 |
| - for _, stopState := range stopStates { |
315 |
| - if task.Status.State == stopState { |
316 |
| - stop = true |
317 |
| - break |
318 |
| - } |
319 |
| - } |
320 |
| - |
321 |
| - if stop { |
322 |
| - fmt.Printf("\n\n") |
323 |
| - fmt.Printf("Exit code: %d\n", task.Status.ContainerStatus.ExitCode) |
324 |
| - fmt.Printf("State: %s\n", task.Status.State) |
325 |
| - fmt.Printf("\n\n") |
326 |
| - |
327 |
| - exitCode = task.Status.ContainerStatus.ExitCode |
328 |
| - |
329 |
| - if exitCode == 0 && task.Status.State == swarm.TaskStateRejected { |
330 |
| - exitCode = 255 // force non-zero exit for task rejected |
331 |
| - } |
332 |
| - |
333 |
| - if showLogs { |
334 |
| - fmt.Println("Printing service logs") |
335 |
| - } |
336 |
| - |
337 |
| - if showLogs { |
338 |
| - logRequest, err := c.ServiceLogs(context.Background(), id, types.ContainerLogsOptions{ |
339 |
| - Follow: false, |
340 |
| - ShowStdout: true, |
341 |
| - ShowStderr: true, |
342 |
| - Timestamps: true, |
343 |
| - Details: false, |
344 |
| - Tail: "all", |
345 |
| - }) |
346 |
| - |
347 |
| - if err != nil { |
348 |
| - fmt.Printf("Unable to pull service logs.\nError: %s\n", err) |
349 |
| - } else { |
350 |
| - defer logRequest.Close() |
351 |
| - |
352 |
| - // , ShowStderr: true, ShowStdout: true}) |
353 |
| - res, _ := ioutil.ReadAll(logRequest) |
354 |
| - |
355 |
| - fmt.Println(string(res[:])) |
356 |
| - } |
357 |
| - } |
358 |
| - |
359 |
| - if removeService { |
360 |
| - fmt.Println("Removing service...") |
361 |
| - if err := c.ServiceRemove(context.Background(), id); err != nil { |
362 |
| - fmt.Fprintln(os.Stderr, err) |
363 |
| - } |
364 |
| - } |
365 |
| - |
366 |
| - done = true |
367 |
| - break |
368 |
| - } else { |
369 |
| - fmt.Printf(".") |
370 |
| - } |
371 |
| - } |
372 |
| - return exitCode, done |
373 |
| -} |
0 commit comments