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  }