github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/container/remove.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package container 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "os" 25 "runtime" 26 "syscall" 27 28 "github.com/containerd/containerd" 29 "github.com/containerd/containerd/cio" 30 "github.com/containerd/containerd/errdefs" 31 "github.com/containerd/containerd/namespaces" 32 "github.com/containerd/log" 33 "github.com/containerd/nerdctl/v2/pkg/api/types" 34 "github.com/containerd/nerdctl/v2/pkg/clientutil" 35 "github.com/containerd/nerdctl/v2/pkg/cmd/volume" 36 "github.com/containerd/nerdctl/v2/pkg/containerutil" 37 "github.com/containerd/nerdctl/v2/pkg/dnsutil/hostsstore" 38 "github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker" 39 "github.com/containerd/nerdctl/v2/pkg/ipcutil" 40 "github.com/containerd/nerdctl/v2/pkg/labels" 41 "github.com/containerd/nerdctl/v2/pkg/namestore" 42 ) 43 44 var _ error = ErrContainerStatus{} 45 46 // ErrContainerStatus represents an error that container is in a status unexpected 47 // by the caller. E.g., remove a non-stoped/non-created container without force. 48 type ErrContainerStatus struct { 49 ID string 50 Status containerd.ProcessStatus 51 } 52 53 func (e ErrContainerStatus) Error() string { 54 return fmt.Sprintf("container %s is in %v status", e.ID, e.Status) 55 } 56 57 // NewStatusError creates an ErrContainerStatus from container id and status. 58 func NewStatusError(id string, status containerd.ProcessStatus) error { 59 return ErrContainerStatus{ 60 ID: id, 61 Status: status, 62 } 63 } 64 65 // Remove removes a list of `containers`. 66 func Remove(ctx context.Context, client *containerd.Client, containers []string, options types.ContainerRemoveOptions) error { 67 walker := &containerwalker.ContainerWalker{ 68 Client: client, 69 OnFound: func(ctx context.Context, found containerwalker.Found) error { 70 if found.MatchCount > 1 { 71 return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req) 72 } 73 if err := RemoveContainer(ctx, found.Container, options.GOptions, options.Force, options.Volumes, client); err != nil { 74 if errors.As(err, &ErrContainerStatus{}) { 75 err = fmt.Errorf("%s. unpause/stop container first or force removal", err) 76 } 77 return err 78 } 79 _, err := fmt.Fprintln(options.Stdout, found.Req) 80 return err 81 }, 82 } 83 84 err := walker.WalkAll(ctx, containers, true) 85 if err != nil && options.Force { 86 log.G(ctx).Error(err) 87 return nil 88 } 89 return err 90 } 91 92 // RemoveContainer removes a container from containerd store. 93 func RemoveContainer(ctx context.Context, c containerd.Container, globalOptions types.GlobalCommandOptions, force bool, removeAnonVolumes bool, client *containerd.Client) (retErr error) { 94 // defer the storage of remove error in the dedicated label 95 defer func() { 96 if retErr != nil { 97 containerutil.UpdateErrorLabel(ctx, c, retErr) 98 } 99 }() 100 ns, err := namespaces.NamespaceRequired(ctx) 101 if err != nil { 102 return err 103 } 104 id := c.ID() 105 l, err := c.Labels(ctx) 106 if err != nil { 107 return err 108 } 109 ipc, err := ipcutil.DecodeIPCLabel(l[labels.IPC]) 110 if err != nil { 111 return err 112 } 113 err = ipcutil.CleanUp(ipc) 114 if err != nil { 115 return err 116 } 117 stateDir := l[labels.StateDir] 118 name := l[labels.Name] 119 dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address) 120 if err != nil { 121 return err 122 } 123 namst, err := namestore.New(dataStore, ns) 124 if err != nil { 125 return err 126 } 127 128 defer func() { 129 if errdefs.IsNotFound(retErr) { 130 retErr = nil 131 } 132 if retErr != nil { 133 return 134 } 135 if err := os.RemoveAll(stateDir); err != nil { 136 log.G(ctx).WithError(err).Warnf("failed to remove container state dir %s", stateDir) 137 } 138 // enforce release name here in case the poststop hook name release fails 139 if name != "" { 140 if err := namst.Release(name, id); err != nil { 141 log.G(ctx).WithError(err).Warnf("failed to release container name %s", name) 142 } 143 } 144 if err := hostsstore.DeallocHostsFile(dataStore, ns, id); err != nil { 145 log.G(ctx).WithError(err).Warnf("failed to remove hosts file for container %q", id) 146 } 147 }() 148 149 // volume removal is not handled by the poststop hook lifecycle because it depends on removeAnonVolumes option 150 if anonVolumesJSON, ok := l[labels.AnonymousVolumes]; ok && removeAnonVolumes { 151 var anonVolumes []string 152 if err := json.Unmarshal([]byte(anonVolumesJSON), &anonVolumes); err != nil { 153 return err 154 } 155 volStore, err := volume.Store(globalOptions.Namespace, globalOptions.DataRoot, globalOptions.Address) 156 if err != nil { 157 return err 158 } 159 defer func() { 160 if _, err := volStore.Remove(anonVolumes); err != nil { 161 log.G(ctx).WithError(err).Warnf("failed to remove anonymous volumes %v", anonVolumes) 162 } 163 }() 164 } 165 166 task, err := c.Task(ctx, cio.Load) 167 if err != nil { 168 if errdefs.IsNotFound(err) { 169 if c.Delete(ctx, containerd.WithSnapshotCleanup) != nil { 170 return c.Delete(ctx) 171 } 172 } 173 return err 174 } 175 176 status, err := task.Status(ctx) 177 if err != nil { 178 if errdefs.IsNotFound(err) { 179 return nil 180 } 181 return err 182 } 183 184 // NOTE: on non-Windows platforms, network cleanup is performed by OCI hooks. 185 // Seeing as though Windows does not currently support OCI hooks, we must explicitly 186 // perform the network cleanup from the main nerdctl executable. 187 if runtime.GOOS == "windows" { 188 spec, err := c.Spec(ctx) 189 if err != nil { 190 return err 191 } 192 193 netOpts, err := containerutil.NetworkOptionsFromSpec(spec) 194 if err != nil { 195 return fmt.Errorf("failed to load container networking options from specs: %s", err) 196 } 197 198 networkManager, err := containerutil.NewNetworkingOptionsManager(globalOptions, netOpts, client) 199 if err != nil { 200 return fmt.Errorf("failed to instantiate network options manager: %s", err) 201 } 202 203 if err := networkManager.CleanupNetworking(ctx, c); err != nil { 204 log.G(ctx).WithError(err).Warnf("failed to clean up container networking: %s", err) 205 } 206 } 207 208 switch status.Status { 209 case containerd.Created, containerd.Stopped: 210 if _, err := task.Delete(ctx); err != nil && !errdefs.IsNotFound(err) { 211 return fmt.Errorf("failed to delete task %v: %w", id, err) 212 } 213 case containerd.Paused: 214 if !force { 215 return NewStatusError(id, status.Status) 216 } 217 _, err := task.Delete(ctx, containerd.WithProcessKill) 218 if err != nil && !errdefs.IsNotFound(err) { 219 return fmt.Errorf("failed to delete task %v: %w", id, err) 220 } 221 // default is the case, when status.Status = containerd.Running 222 default: 223 if !force { 224 return NewStatusError(id, status.Status) 225 } 226 if err := task.Kill(ctx, syscall.SIGKILL); err != nil { 227 log.G(ctx).WithError(err).Warnf("failed to send SIGKILL") 228 } 229 es, err := task.Wait(ctx) 230 if err == nil { 231 <-es 232 } 233 _, err = task.Delete(ctx, containerd.WithProcessKill) 234 if err != nil && !errdefs.IsNotFound(err) { 235 log.G(ctx).WithError(err).Warnf("failed to delete task %v", id) 236 } 237 } 238 var delOpts []containerd.DeleteOpts 239 if _, err := c.Image(ctx); err == nil { 240 delOpts = append(delOpts, containerd.WithSnapshotCleanup) 241 } 242 243 if err := c.Delete(ctx, delOpts...); err != nil { 244 return err 245 } 246 return err 247 }