github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/stack/kubernetes/deploy.go (about) 1 package kubernetes 2 3 import ( 4 "fmt" 5 "io" 6 7 "github.com/docker/cli/cli/command/stack/options" 8 composetypes "github.com/docker/cli/cli/compose/types" 9 "github.com/docker/cli/cli/streams" 10 "github.com/morikuni/aec" 11 corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 12 ) 13 14 // RunDeploy is the kubernetes implementation of docker stack deploy 15 func RunDeploy(dockerCli *KubeCli, opts options.Deploy, cfg *composetypes.Config) error { 16 cmdOut := dockerCli.Out() 17 18 // Initialize clients 19 composeClient, err := dockerCli.composeClient() 20 if err != nil { 21 return err 22 } 23 stacks, err := composeClient.Stacks(false) 24 if err != nil { 25 return err 26 } 27 28 stack, err := stacks.FromCompose(dockerCli.Err(), opts.Namespace, cfg) 29 if err != nil { 30 return err 31 } 32 33 configMaps := composeClient.ConfigMaps() 34 secrets := composeClient.Secrets() 35 services := composeClient.Services() 36 37 if err := stacks.IsColliding(services, stack); err != nil { 38 return err 39 } 40 41 if err := createResources(stack, stacks, configMaps, secrets); err != nil { 42 return err 43 } 44 45 fmt.Fprintln(cmdOut, "Waiting for the stack to be stable and running...") 46 v1beta1Cli, err := dockerCli.stacksv1beta1() 47 if err != nil { 48 return err 49 } 50 51 pods := composeClient.Pods() 52 watcher := &deployWatcher{ 53 stacks: v1beta1Cli, 54 pods: pods, 55 } 56 statusUpdates := make(chan serviceStatus) 57 displayDone := make(chan struct{}) 58 go func() { 59 defer close(displayDone) 60 display := newStatusDisplay(dockerCli.Out()) 61 for status := range statusUpdates { 62 display.OnStatus(status) 63 } 64 }() 65 66 err = watcher.Watch(stack.Name, stack.getServices(), statusUpdates) 67 close(statusUpdates) 68 <-displayDone 69 if err != nil { 70 return err 71 } 72 fmt.Fprintf(cmdOut, "\nStack %s is stable and running\n\n", stack.Name) 73 return nil 74 75 } 76 77 func createResources(stack Stack, stacks StackClient, configMaps corev1.ConfigMapInterface, secrets corev1.SecretInterface) error { 78 var childResources []childResource 79 80 cr, err := stack.createFileBasedConfigMaps(configMaps) 81 childResources = append(childResources, cr...) // make sure we collect childresources already created in case of failure 82 if err != nil { 83 deleteChildResources(childResources) 84 return err 85 } 86 87 cr, err = stack.createFileBasedSecrets(secrets) 88 childResources = append(childResources, cr...) // make sure we collect childresources already created in case of failure 89 if err != nil { 90 deleteChildResources(childResources) 91 return err 92 } 93 94 return stacks.CreateOrUpdate(stack, childResources) 95 } 96 97 type statusDisplay interface { 98 OnStatus(serviceStatus) 99 } 100 type metaServiceState string 101 102 const ( 103 metaServiceStateReady = metaServiceState("Ready") 104 metaServiceStatePending = metaServiceState("Pending") 105 metaServiceStateFailed = metaServiceState("Failed") 106 ) 107 108 func metaStateFromStatus(status serviceStatus) metaServiceState { 109 switch { 110 case status.podsReady > 0: 111 return metaServiceStateReady 112 case status.podsPending > 0: 113 return metaServiceStatePending 114 default: 115 return metaServiceStateFailed 116 } 117 } 118 119 type forwardOnlyStatusDisplay struct { 120 o *streams.Out 121 states map[string]metaServiceState 122 } 123 124 func (d *forwardOnlyStatusDisplay) OnStatus(status serviceStatus) { 125 state := metaStateFromStatus(status) 126 if d.states[status.name] != state { 127 d.states[status.name] = state 128 fmt.Fprintf(d.o, "%s: %s\n", status.name, state) 129 } 130 } 131 132 type interactiveStatusDisplay struct { 133 o *streams.Out 134 statuses []serviceStatus 135 } 136 137 func (d *interactiveStatusDisplay) OnStatus(status serviceStatus) { 138 b := aec.EmptyBuilder 139 for ix := 0; ix < len(d.statuses); ix++ { 140 b = b.Up(1).EraseLine(aec.EraseModes.All) 141 } 142 b = b.Column(0) 143 fmt.Fprint(d.o, b.ANSI) 144 updated := false 145 for ix, s := range d.statuses { 146 if s.name == status.name { 147 d.statuses[ix] = status 148 s = status 149 updated = true 150 } 151 displayInteractiveServiceStatus(s, d.o) 152 } 153 if !updated { 154 d.statuses = append(d.statuses, status) 155 displayInteractiveServiceStatus(status, d.o) 156 } 157 } 158 159 func displayInteractiveServiceStatus(status serviceStatus, o io.Writer) { 160 state := metaStateFromStatus(status) 161 totalFailed := status.podsFailed + status.podsSucceeded + status.podsUnknown 162 fmt.Fprintf(o, "%[1]s: %[2]s\t\t[pod status: %[3]d/%[6]d ready, %[4]d/%[6]d pending, %[5]d/%[6]d failed]\n", status.name, state, 163 status.podsReady, status.podsPending, totalFailed, status.podsTotal) 164 } 165 166 func newStatusDisplay(o *streams.Out) statusDisplay { 167 if !o.IsTerminal() { 168 return &forwardOnlyStatusDisplay{o: o, states: map[string]metaServiceState{}} 169 } 170 return &interactiveStatusDisplay{o: o} 171 }