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