github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/worker/runtime/container.go (about) 1 package runtime 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "regexp" 8 "time" 9 10 "code.cloudfoundry.org/garden" 11 "github.com/containerd/containerd" 12 "github.com/containerd/containerd/cio" 13 uuid "github.com/nu7hatch/gouuid" 14 "github.com/opencontainers/runtime-spec/specs-go" 15 ) 16 17 const GraceTimeKey = "garden.grace-time" 18 19 type UserNotFoundError struct { 20 User string 21 } 22 23 func (u UserNotFoundError) Error() string { 24 return fmt.Sprintf("user '%s' not found: no matching entries in /etc/passwd", u.User) 25 } 26 27 type Container struct { 28 container containerd.Container 29 killer Killer 30 rootfsManager RootfsManager 31 } 32 33 func NewContainer( 34 container containerd.Container, 35 killer Killer, 36 rootfsManager RootfsManager, 37 ) *Container { 38 return &Container{ 39 container: container, 40 killer: killer, 41 rootfsManager: rootfsManager, 42 } 43 } 44 45 var _ garden.Container = (*Container)(nil) 46 47 func (c *Container) Handle() string { 48 return c.container.ID() 49 } 50 51 // Stop stops a container. 52 // 53 func (c *Container) Stop(kill bool) error { 54 ctx := context.Background() 55 56 task, err := c.container.Task(ctx, cio.Load) 57 if err != nil { 58 return fmt.Errorf("task lookup: %w", err) 59 } 60 61 behaviour := KillGracefully 62 if kill { 63 behaviour = KillUngracefully 64 } 65 66 err = c.killer.Kill(ctx, task, behaviour) 67 if err != nil { 68 return fmt.Errorf("kill: %w", err) 69 } 70 71 return nil 72 } 73 74 // Run a process inside the container. 75 // 76 func (c *Container) Run( 77 spec garden.ProcessSpec, 78 processIO garden.ProcessIO, 79 ) (garden.Process, error) { 80 ctx := context.Background() 81 82 containerSpec, err := c.container.Spec(ctx) 83 if err != nil { 84 return nil, fmt.Errorf("container spec: %w", err) 85 } 86 87 procSpec, err := c.setupContainerdProcSpec(spec, *containerSpec) 88 if err != nil { 89 return nil, err 90 } 91 92 err = c.rootfsManager.SetupCwd(containerSpec.Root.Path, procSpec.Cwd) 93 if err != nil { 94 return nil, fmt.Errorf("setup cwd: %w", err) 95 } 96 97 task, err := c.container.Task(ctx, nil) 98 if err != nil { 99 return nil, fmt.Errorf("task retrieval: %w", err) 100 } 101 102 id := procID(spec) 103 cioOpts := containerdCIO(processIO, spec.TTY != nil) 104 105 proc, err := task.Exec(ctx, id, &procSpec, cio.NewCreator(cioOpts...)) 106 if err != nil { 107 return nil, fmt.Errorf("task exec: %w", err) 108 } 109 110 exitStatusC, err := proc.Wait(ctx) 111 if err != nil { 112 return nil, fmt.Errorf("proc wait: %w", err) 113 } 114 115 err = proc.Start(ctx) 116 if err != nil { 117 if isNoSuchExecutable(err) { 118 return nil, garden.ExecutableNotFoundError{Message: err.Error()} 119 } 120 return nil, fmt.Errorf("proc start: %w", err) 121 } 122 123 err = proc.CloseIO(ctx, containerd.WithStdinCloser) 124 if err != nil { 125 return nil, fmt.Errorf("proc closeio: %w", err) 126 } 127 128 return NewProcess(proc, exitStatusC), nil 129 } 130 131 // Attach starts streaming the output back to the client from a specified process. 132 // 133 func (c *Container) Attach(pid string, processIO garden.ProcessIO) (process garden.Process, err error) { 134 ctx := context.Background() 135 136 if pid == "" { 137 return nil, ErrInvalidInput("empty pid") 138 } 139 140 task, err := c.container.Task(ctx, cio.Load) 141 if err != nil { 142 return nil, fmt.Errorf("task: %w", err) 143 } 144 145 cioOpts := []cio.Opt{ 146 cio.WithStreams( 147 processIO.Stdin, 148 processIO.Stdout, 149 processIO.Stderr, 150 ), 151 } 152 153 proc, err := task.LoadProcess(ctx, pid, cio.NewAttach(cioOpts...)) 154 if err != nil { 155 return nil, fmt.Errorf("load proc: %w", err) 156 } 157 158 status, err := proc.Status(ctx) 159 if err != nil { 160 return nil, fmt.Errorf("proc status: %w", err) 161 } 162 163 if status.Status != containerd.Running { 164 return nil, fmt.Errorf("proc not running: status = %s", status.Status) 165 } 166 167 exitStatusC, err := proc.Wait(ctx) 168 if err != nil { 169 return nil, fmt.Errorf("proc wait: %w", err) 170 } 171 172 return NewProcess(proc, exitStatusC), nil 173 } 174 175 // Properties returns the current set of properties 176 // 177 func (c *Container) Properties() (garden.Properties, error) { 178 ctx := context.Background() 179 180 labels, err := c.container.Labels(ctx) 181 if err != nil { 182 return garden.Properties{}, fmt.Errorf("labels retrieval: %w", err) 183 } 184 185 return labels, nil 186 } 187 188 // Property returns the value of the property with the specified name. 189 // 190 func (c *Container) Property(name string) (string, error) { 191 properties, err := c.Properties() 192 if err != nil { 193 return "", err 194 } 195 196 v, found := properties[name] 197 if !found { 198 return "", ErrNotFound(name) 199 } 200 201 return v, nil 202 } 203 204 // Set a named property on a container to a specified value. 205 // 206 func (c *Container) SetProperty(name string, value string) error { 207 labelSet := map[string]string{ 208 name: value, 209 } 210 211 _, err := c.container.SetLabels(context.Background(), labelSet) 212 if err != nil { 213 return fmt.Errorf("set label: %w", err) 214 } 215 216 return nil 217 } 218 219 // RemoveProperty - Not Implemented 220 func (c *Container) RemoveProperty(name string) (err error) { 221 err = ErrNotImplemented 222 return 223 } 224 225 // Info - Not Implemented 226 func (c *Container) Info() (info garden.ContainerInfo, err error) { 227 err = ErrNotImplemented 228 return 229 } 230 231 // Metrics - Not Implemented 232 func (c *Container) Metrics() (metrics garden.Metrics, err error) { 233 err = ErrNotImplemented 234 return 235 } 236 237 // StreamIn - Not Implemented 238 func (c *Container) StreamIn(spec garden.StreamInSpec) (err error) { 239 err = ErrNotImplemented 240 return 241 } 242 243 // StreamOut - Not Implemented 244 func (c *Container) StreamOut(spec garden.StreamOutSpec) (readCloser io.ReadCloser, err error) { 245 err = ErrNotImplemented 246 return 247 } 248 249 // SetGraceTime stores the grace time as a containerd label with key "garden.grace-time" 250 // 251 func (c *Container) SetGraceTime(graceTime time.Duration) error { 252 err := c.SetProperty(GraceTimeKey, fmt.Sprintf("%d", graceTime)) 253 if err != nil { 254 return fmt.Errorf("set grace time: %w", err) 255 } 256 257 return nil 258 } 259 260 // CurrentBandwidthLimits returns no limits (achieves parity with Guardian) 261 func (c *Container) CurrentBandwidthLimits() (garden.BandwidthLimits, error) { 262 return garden.BandwidthLimits{}, nil 263 } 264 265 // CurrentCPULimits returns the CPU shares allocated to the container 266 func (c *Container) CurrentCPULimits() (garden.CPULimits, error) { 267 spec, err := c.container.Spec(context.Background()) 268 if err != nil { 269 return garden.CPULimits{}, err 270 } 271 272 if spec == nil || 273 spec.Linux == nil || 274 spec.Linux.Resources == nil || 275 spec.Linux.Resources.CPU == nil || 276 spec.Linux.Resources.CPU.Shares == nil { 277 return garden.CPULimits{}, nil 278 } 279 280 return garden.CPULimits{ 281 Weight: *spec.Linux.Resources.CPU.Shares, 282 }, nil 283 } 284 285 // CurrentDiskLimits returns no limits (achieves parity with Guardian) 286 func (c *Container) CurrentDiskLimits() (garden.DiskLimits, error) { 287 return garden.DiskLimits{}, nil 288 } 289 290 // CurrentMemoryLimits returns the memory limit in bytes allocated to the container 291 func (c *Container) CurrentMemoryLimits() (limits garden.MemoryLimits, err error) { 292 spec, err := c.container.Spec(context.Background()) 293 if err != nil { 294 return garden.MemoryLimits{}, err 295 } 296 297 if spec == nil || 298 spec.Linux == nil || 299 spec.Linux.Resources == nil || 300 spec.Linux.Resources.Memory == nil || 301 spec.Linux.Resources.Memory.Limit == nil { 302 return garden.MemoryLimits{}, nil 303 } 304 305 return garden.MemoryLimits{ 306 LimitInBytes: uint64(*spec.Linux.Resources.Memory.Limit), 307 }, nil 308 } 309 310 // NetIn - Not Implemented 311 func (c *Container) NetIn(hostPort, containerPort uint32) (a, b uint32, err error) { 312 err = ErrNotImplemented 313 return 314 } 315 316 // NetOut - Not Implemented 317 func (c *Container) NetOut(netOutRule garden.NetOutRule) (err error) { 318 err = ErrNotImplemented 319 return 320 } 321 322 // BulkNetOut - Not Implemented 323 func (c *Container) BulkNetOut(netOutRules []garden.NetOutRule) (err error) { 324 err = ErrNotImplemented 325 return 326 } 327 328 func procID(gdnProcSpec garden.ProcessSpec) string { 329 id := gdnProcSpec.ID 330 if id == "" { 331 uuid, err := uuid.NewV4() 332 if err != nil { 333 panic(fmt.Errorf("uuid gen: %w", err)) 334 } 335 336 id = uuid.String() 337 } 338 339 return id 340 } 341 342 func (c *Container) setupContainerdProcSpec(gdnProcSpec garden.ProcessSpec, containerSpec specs.Spec) (specs.Process, error) { 343 procSpec := containerSpec.Process 344 345 procSpec.Args = append([]string{gdnProcSpec.Path}, gdnProcSpec.Args...) 346 procSpec.Env = append(procSpec.Env, gdnProcSpec.Env...) 347 348 cwd := gdnProcSpec.Dir 349 if cwd == "" { 350 cwd = "/" 351 } 352 353 procSpec.Cwd = cwd 354 355 if gdnProcSpec.TTY != nil { 356 procSpec.Terminal = true 357 358 if gdnProcSpec.TTY.WindowSize != nil { 359 procSpec.ConsoleSize = &specs.Box{ 360 Width: uint(gdnProcSpec.TTY.WindowSize.Columns), 361 Height: uint(gdnProcSpec.TTY.WindowSize.Rows), 362 } 363 } 364 } 365 366 if gdnProcSpec.User != "" { 367 var ok bool 368 var err error 369 procSpec.User, ok, err = c.rootfsManager.LookupUser(containerSpec.Root.Path, gdnProcSpec.User) 370 if err != nil { 371 return specs.Process{}, fmt.Errorf("lookup user: %w", err) 372 } 373 if !ok { 374 return specs.Process{}, UserNotFoundError{User: gdnProcSpec.User} 375 } 376 377 setUserEnv := fmt.Sprintf("USER=%s", gdnProcSpec.User) 378 procSpec.Env = append(procSpec.Env, setUserEnv) 379 } 380 return *procSpec, nil 381 } 382 383 func containerdCIO(gdnProcIO garden.ProcessIO, tty bool) []cio.Opt { 384 cioOpts := []cio.Opt{ 385 cio.WithStreams( 386 gdnProcIO.Stdin, 387 gdnProcIO.Stdout, 388 gdnProcIO.Stderr, 389 ), 390 } 391 392 if tty { 393 cioOpts = append(cioOpts, cio.WithTerminal) 394 } 395 396 return cioOpts 397 } 398 399 func isNoSuchExecutable(err error) bool { 400 noSuchFile := regexp.MustCompile(`starting container process caused: exec: .*: stat .*: no such file or directory`) 401 executableNotFound := regexp.MustCompile(`starting container process caused: exec: .*: executable file not found in \$PATH`) 402 403 return noSuchFile.MatchString(err.Error()) || executableNotFound.MatchString(err.Error()) 404 }