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 }