github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/command/stack/swarm/remove.go (about)

     1  package swarm
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/api/types/swarm"
    11  	"github.com/docker/docker/api/types/versions"
    12  	apiclient "github.com/docker/docker/client"
    13  	"github.com/khulnasoft/cli/cli/command"
    14  	"github.com/khulnasoft/cli/cli/command/stack/options"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  // RunRemove is the swarm implementation of docker stack remove
    19  func RunRemove(ctx context.Context, dockerCli command.Cli, opts options.Remove) error {
    20  	client := dockerCli.Client()
    21  
    22  	var errs []string
    23  	for _, namespace := range opts.Namespaces {
    24  		services, err := getStackServices(ctx, client, namespace)
    25  		if err != nil {
    26  			return err
    27  		}
    28  
    29  		networks, err := getStackNetworks(ctx, client, namespace)
    30  		if err != nil {
    31  			return err
    32  		}
    33  
    34  		var secrets []swarm.Secret
    35  		if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.25") {
    36  			secrets, err = getStackSecrets(ctx, client, namespace)
    37  			if err != nil {
    38  				return err
    39  			}
    40  		}
    41  
    42  		var configs []swarm.Config
    43  		if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.30") {
    44  			configs, err = getStackConfigs(ctx, client, namespace)
    45  			if err != nil {
    46  				return err
    47  			}
    48  		}
    49  
    50  		if len(services)+len(networks)+len(secrets)+len(configs) == 0 {
    51  			fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", namespace)
    52  			continue
    53  		}
    54  
    55  		hasError := removeServices(ctx, dockerCli, services)
    56  		hasError = removeSecrets(ctx, dockerCli, secrets) || hasError
    57  		hasError = removeConfigs(ctx, dockerCli, configs) || hasError
    58  		hasError = removeNetworks(ctx, dockerCli, networks) || hasError
    59  
    60  		if hasError {
    61  			errs = append(errs, fmt.Sprintf("Failed to remove some resources from stack: %s", namespace))
    62  			continue
    63  		}
    64  
    65  		if !opts.Detach {
    66  			err = waitOnTasks(ctx, client, namespace)
    67  			if err != nil {
    68  				errs = append(errs, fmt.Sprintf("Failed to wait on tasks of stack: %s: %s", namespace, err))
    69  			}
    70  		}
    71  	}
    72  
    73  	if len(errs) > 0 {
    74  		return errors.Errorf(strings.Join(errs, "\n"))
    75  	}
    76  	return nil
    77  }
    78  
    79  func sortServiceByName(services []swarm.Service) func(i, j int) bool {
    80  	return func(i, j int) bool {
    81  		return services[i].Spec.Name < services[j].Spec.Name
    82  	}
    83  }
    84  
    85  func removeServices(
    86  	ctx context.Context,
    87  	dockerCli command.Cli,
    88  	services []swarm.Service,
    89  ) bool {
    90  	var hasError bool
    91  	sort.Slice(services, sortServiceByName(services))
    92  	for _, service := range services {
    93  		fmt.Fprintf(dockerCli.Out(), "Removing service %s\n", service.Spec.Name)
    94  		if err := dockerCli.Client().ServiceRemove(ctx, service.ID); err != nil {
    95  			hasError = true
    96  			fmt.Fprintf(dockerCli.Err(), "Failed to remove service %s: %s", service.ID, err)
    97  		}
    98  	}
    99  	return hasError
   100  }
   101  
   102  func removeNetworks(
   103  	ctx context.Context,
   104  	dockerCli command.Cli,
   105  	networks []types.NetworkResource,
   106  ) bool {
   107  	var hasError bool
   108  	for _, network := range networks {
   109  		fmt.Fprintf(dockerCli.Out(), "Removing network %s\n", network.Name)
   110  		if err := dockerCli.Client().NetworkRemove(ctx, network.ID); err != nil {
   111  			hasError = true
   112  			fmt.Fprintf(dockerCli.Err(), "Failed to remove network %s: %s", network.ID, err)
   113  		}
   114  	}
   115  	return hasError
   116  }
   117  
   118  func removeSecrets(
   119  	ctx context.Context,
   120  	dockerCli command.Cli,
   121  	secrets []swarm.Secret,
   122  ) bool {
   123  	var hasError bool
   124  	for _, secret := range secrets {
   125  		fmt.Fprintf(dockerCli.Out(), "Removing secret %s\n", secret.Spec.Name)
   126  		if err := dockerCli.Client().SecretRemove(ctx, secret.ID); err != nil {
   127  			hasError = true
   128  			fmt.Fprintf(dockerCli.Err(), "Failed to remove secret %s: %s", secret.ID, err)
   129  		}
   130  	}
   131  	return hasError
   132  }
   133  
   134  func removeConfigs(
   135  	ctx context.Context,
   136  	dockerCli command.Cli,
   137  	configs []swarm.Config,
   138  ) bool {
   139  	var hasError bool
   140  	for _, config := range configs {
   141  		fmt.Fprintf(dockerCli.Out(), "Removing config %s\n", config.Spec.Name)
   142  		if err := dockerCli.Client().ConfigRemove(ctx, config.ID); err != nil {
   143  			hasError = true
   144  			fmt.Fprintf(dockerCli.Err(), "Failed to remove config %s: %s", config.ID, err)
   145  		}
   146  	}
   147  	return hasError
   148  }
   149  
   150  var numberedStates = map[swarm.TaskState]int64{
   151  	swarm.TaskStateNew:       1,
   152  	swarm.TaskStateAllocated: 2,
   153  	swarm.TaskStatePending:   3,
   154  	swarm.TaskStateAssigned:  4,
   155  	swarm.TaskStateAccepted:  5,
   156  	swarm.TaskStatePreparing: 6,
   157  	swarm.TaskStateReady:     7,
   158  	swarm.TaskStateStarting:  8,
   159  	swarm.TaskStateRunning:   9,
   160  	swarm.TaskStateComplete:  10,
   161  	swarm.TaskStateShutdown:  11,
   162  	swarm.TaskStateFailed:    12,
   163  	swarm.TaskStateRejected:  13,
   164  }
   165  
   166  func terminalState(state swarm.TaskState) bool {
   167  	return numberedStates[state] > numberedStates[swarm.TaskStateRunning]
   168  }
   169  
   170  func waitOnTasks(ctx context.Context, client apiclient.APIClient, namespace string) error {
   171  	terminalStatesReached := 0
   172  	for {
   173  		tasks, err := getStackTasks(ctx, client, namespace)
   174  		if err != nil {
   175  			return fmt.Errorf("failed to get tasks: %w", err)
   176  		}
   177  
   178  		for _, task := range tasks {
   179  			if terminalState(task.Status.State) {
   180  				terminalStatesReached++
   181  				break
   182  			}
   183  		}
   184  
   185  		if terminalStatesReached == len(tasks) {
   186  			break
   187  		}
   188  	}
   189  	return nil
   190  }