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