github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/container/view.go (about) 1 package container // import "github.com/docker/docker/container" 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "strings" 9 "time" 10 11 "github.com/containerd/log" 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/network" 14 "github.com/docker/docker/errdefs" 15 "github.com/docker/go-connections/nat" 16 memdb "github.com/hashicorp/go-memdb" 17 ) 18 19 const ( 20 memdbContainersTable = "containers" 21 memdbNamesTable = "names" 22 memdbIDIndex = "id" 23 memdbIDIndexPrefix = "id_prefix" 24 memdbContainerIDIndex = "containerid" 25 ) 26 27 var ( 28 // ErrNameReserved is an error which is returned when a name is requested to be reserved that already is reserved 29 ErrNameReserved = errors.New("name is reserved") 30 // ErrNameNotReserved is an error which is returned when trying to find a name that is not reserved 31 ErrNameNotReserved = errors.New("name is not reserved") 32 ) 33 34 // Snapshot is a read only view for Containers. It holds all information necessary to serve container queries in a 35 // versioned ACID in-memory store. 36 type Snapshot struct { 37 types.Container 38 39 // additional info queries need to filter on 40 // preserve nanosec resolution for queries 41 CreatedAt time.Time 42 StartedAt time.Time 43 Name string 44 Pid int 45 ExitCode int 46 Running bool 47 Paused bool 48 Managed bool 49 ExposedPorts nat.PortSet 50 PortBindings nat.PortSet 51 Health string 52 HostConfig struct { 53 Isolation string 54 } 55 } 56 57 // nameAssociation associates a container id with a name. 58 type nameAssociation struct { 59 // name is the name to associate. Note that name is the primary key 60 // ("id" in memdb). 61 name string 62 containerID string 63 } 64 65 var schema = &memdb.DBSchema{ 66 Tables: map[string]*memdb.TableSchema{ 67 memdbContainersTable: { 68 Name: memdbContainersTable, 69 Indexes: map[string]*memdb.IndexSchema{ 70 memdbIDIndex: { 71 Name: memdbIDIndex, 72 Unique: true, 73 Indexer: &containerByIDIndexer{}, 74 }, 75 }, 76 }, 77 memdbNamesTable: { 78 Name: memdbNamesTable, 79 Indexes: map[string]*memdb.IndexSchema{ 80 // Used for names, because "id" is the primary key in memdb. 81 memdbIDIndex: { 82 Name: memdbIDIndex, 83 Unique: true, 84 Indexer: &namesByNameIndexer{}, 85 }, 86 memdbContainerIDIndex: { 87 Name: memdbContainerIDIndex, 88 Indexer: &namesByContainerIDIndexer{}, 89 }, 90 }, 91 }, 92 }, 93 } 94 95 // ViewDB provides an in-memory transactional (ACID) container store. 96 type ViewDB struct { 97 store *memdb.MemDB 98 } 99 100 // NewViewDB provides the default implementation, with the default schema 101 func NewViewDB() (*ViewDB, error) { 102 store, err := memdb.NewMemDB(schema) 103 if err != nil { 104 return nil, errdefs.System(err) 105 } 106 return &ViewDB{store: store}, nil 107 } 108 109 // GetByPrefix returns a container with the given ID prefix. It returns an 110 // error if an empty prefix was given or if multiple containers match the prefix. 111 func (db *ViewDB) GetByPrefix(s string) (string, error) { 112 if s == "" { 113 return "", errdefs.InvalidParameter(errors.New("prefix can't be empty")) 114 } 115 iter, err := db.store.Txn(false).Get(memdbContainersTable, memdbIDIndexPrefix, s) 116 if err != nil { 117 return "", errdefs.System(err) 118 } 119 120 var id string 121 for { 122 item := iter.Next() 123 if item == nil { 124 break 125 } 126 if id != "" { 127 return "", errdefs.InvalidParameter(errors.New("multiple IDs found with provided prefix: " + s)) 128 } 129 id = item.(*Container).ID 130 } 131 132 if id != "" { 133 return id, nil 134 } 135 136 return "", errdefs.NotFound(errors.New("No such container: " + s)) 137 } 138 139 // Snapshot provides a consistent read-only view of the database. 140 func (db *ViewDB) Snapshot() *View { 141 return &View{ 142 txn: db.store.Txn(false), 143 } 144 } 145 146 func (db *ViewDB) withTxn(cb func(*memdb.Txn) error) error { 147 txn := db.store.Txn(true) 148 err := cb(txn) 149 if err != nil { 150 txn.Abort() 151 return errdefs.System(err) 152 } 153 txn.Commit() 154 return nil 155 } 156 157 // Save atomically updates the in-memory store state for a Container. 158 // Only read only (deep) copies of containers may be passed in. 159 func (db *ViewDB) Save(c *Container) error { 160 return db.withTxn(func(txn *memdb.Txn) error { 161 return txn.Insert(memdbContainersTable, c) 162 }) 163 } 164 165 // Delete removes an item by ID 166 func (db *ViewDB) Delete(c *Container) error { 167 return db.withTxn(func(txn *memdb.Txn) error { 168 view := &View{txn: txn} 169 names := view.getNames(c.ID) 170 171 for _, name := range names { 172 txn.Delete(memdbNamesTable, nameAssociation{name: name}) 173 } 174 175 // Ignore error - the container may not actually exist in the 176 // db, but we still need to clean up associated names. 177 txn.Delete(memdbContainersTable, NewBaseContainer(c.ID, c.Root)) 178 return nil 179 }) 180 } 181 182 // ReserveName registers a container ID to a name 183 // ReserveName is idempotent 184 // Attempting to reserve a container ID to a name that already exists results in an `ErrNameReserved` 185 // A name reservation is globally unique 186 func (db *ViewDB) ReserveName(name, containerID string) error { 187 return db.withTxn(func(txn *memdb.Txn) error { 188 s, err := txn.First(memdbNamesTable, memdbIDIndex, name) 189 if err != nil { 190 return errdefs.System(err) 191 } 192 if s != nil { 193 if s.(nameAssociation).containerID != containerID { 194 return ErrNameReserved 195 } 196 return nil 197 } 198 return txn.Insert(memdbNamesTable, nameAssociation{name: name, containerID: containerID}) 199 }) 200 } 201 202 // ReleaseName releases the reserved name 203 // Once released, a name can be reserved again 204 func (db *ViewDB) ReleaseName(name string) error { 205 return db.withTxn(func(txn *memdb.Txn) error { 206 return txn.Delete(memdbNamesTable, nameAssociation{name: name}) 207 }) 208 } 209 210 // View provides a consistent read-only view of the database. 211 type View struct { 212 txn *memdb.Txn 213 } 214 215 // All returns a all items in this snapshot. Returned objects must never be modified. 216 func (v *View) All() ([]Snapshot, error) { 217 var all []Snapshot 218 iter, err := v.txn.Get(memdbContainersTable, memdbIDIndex) 219 if err != nil { 220 return nil, errdefs.System(err) 221 } 222 for { 223 item := iter.Next() 224 if item == nil { 225 break 226 } 227 snapshot := v.transform(item.(*Container)) 228 all = append(all, *snapshot) 229 } 230 return all, nil 231 } 232 233 // Get returns an item by id. Returned objects must never be modified. 234 func (v *View) Get(id string) (*Snapshot, error) { 235 s, err := v.txn.First(memdbContainersTable, memdbIDIndex, id) 236 if err != nil { 237 return nil, errdefs.System(err) 238 } 239 if s == nil { 240 return nil, errdefs.NotFound(errors.New("No such container: " + id)) 241 } 242 return v.transform(s.(*Container)), nil 243 } 244 245 // getNames lists all the reserved names for the given container ID. 246 func (v *View) getNames(containerID string) []string { 247 iter, err := v.txn.Get(memdbNamesTable, memdbContainerIDIndex, containerID) 248 if err != nil { 249 return nil 250 } 251 252 var names []string 253 for { 254 item := iter.Next() 255 if item == nil { 256 break 257 } 258 names = append(names, item.(nameAssociation).name) 259 } 260 261 return names 262 } 263 264 // GetID returns the container ID that the passed in name is reserved to. 265 func (v *View) GetID(name string) (string, error) { 266 s, err := v.txn.First(memdbNamesTable, memdbIDIndex, name) 267 if err != nil { 268 return "", errdefs.System(err) 269 } 270 if s == nil { 271 return "", ErrNameNotReserved 272 } 273 return s.(nameAssociation).containerID, nil 274 } 275 276 // GetAllNames returns all registered names. 277 func (v *View) GetAllNames() map[string][]string { 278 iter, err := v.txn.Get(memdbNamesTable, memdbContainerIDIndex) 279 if err != nil { 280 return nil 281 } 282 283 out := make(map[string][]string) 284 for { 285 item := iter.Next() 286 if item == nil { 287 break 288 } 289 assoc := item.(nameAssociation) 290 out[assoc.containerID] = append(out[assoc.containerID], assoc.name) 291 } 292 293 return out 294 } 295 296 // transform maps a (deep) copied Container object to what queries need. 297 // A lock on the Container is not held because these are immutable deep copies. 298 func (v *View) transform(container *Container) *Snapshot { 299 health := types.NoHealthcheck 300 if container.Health != nil { 301 health = container.Health.Status() 302 } 303 snapshot := &Snapshot{ 304 Container: types.Container{ 305 ID: container.ID, 306 Names: v.getNames(container.ID), 307 ImageID: container.ImageID.String(), 308 Ports: []types.Port{}, 309 Mounts: container.GetMountPoints(), 310 State: container.State.StateString(), 311 Status: container.State.String(), 312 Created: container.Created.Unix(), 313 }, 314 CreatedAt: container.Created, 315 StartedAt: container.StartedAt, 316 Name: container.Name, 317 Pid: container.Pid, 318 Managed: container.Managed, 319 ExposedPorts: make(nat.PortSet), 320 PortBindings: make(nat.PortSet), 321 Health: health, 322 Running: container.Running, 323 Paused: container.Paused, 324 ExitCode: container.ExitCode(), 325 } 326 327 if snapshot.Names == nil { 328 // Dead containers will often have no name, so make sure the response isn't null 329 snapshot.Names = []string{} 330 } 331 332 if container.HostConfig != nil { 333 snapshot.Container.HostConfig.NetworkMode = string(container.HostConfig.NetworkMode) 334 snapshot.HostConfig.Isolation = string(container.HostConfig.Isolation) 335 for binding := range container.HostConfig.PortBindings { 336 snapshot.PortBindings[binding] = struct{}{} 337 } 338 } 339 340 if container.Config != nil { 341 snapshot.Image = container.Config.Image 342 snapshot.Labels = container.Config.Labels 343 for exposed := range container.Config.ExposedPorts { 344 snapshot.ExposedPorts[exposed] = struct{}{} 345 } 346 } 347 348 if len(container.Args) > 0 { 349 var args []string 350 for _, arg := range container.Args { 351 if strings.Contains(arg, " ") { 352 args = append(args, fmt.Sprintf("'%s'", arg)) 353 } else { 354 args = append(args, arg) 355 } 356 } 357 argsAsString := strings.Join(args, " ") 358 snapshot.Command = fmt.Sprintf("%s %s", container.Path, argsAsString) 359 } else { 360 snapshot.Command = container.Path 361 } 362 363 snapshot.Ports = []types.Port{} 364 networks := make(map[string]*network.EndpointSettings) 365 if container.NetworkSettings != nil { 366 for name, netw := range container.NetworkSettings.Networks { 367 if netw == nil || netw.EndpointSettings == nil { 368 continue 369 } 370 networks[name] = &network.EndpointSettings{ 371 EndpointID: netw.EndpointID, 372 Gateway: netw.Gateway, 373 IPAddress: netw.IPAddress, 374 IPPrefixLen: netw.IPPrefixLen, 375 IPv6Gateway: netw.IPv6Gateway, 376 GlobalIPv6Address: netw.GlobalIPv6Address, 377 GlobalIPv6PrefixLen: netw.GlobalIPv6PrefixLen, 378 MacAddress: netw.MacAddress, 379 NetworkID: netw.NetworkID, 380 } 381 if netw.IPAMConfig != nil { 382 networks[name].IPAMConfig = &network.EndpointIPAMConfig{ 383 IPv4Address: netw.IPAMConfig.IPv4Address, 384 IPv6Address: netw.IPAMConfig.IPv6Address, 385 } 386 } 387 } 388 for port, bindings := range container.NetworkSettings.Ports { 389 p, err := nat.ParsePort(port.Port()) 390 if err != nil { 391 log.G(context.TODO()).WithError(err).Warn("invalid port map") 392 continue 393 } 394 if len(bindings) == 0 { 395 snapshot.Ports = append(snapshot.Ports, types.Port{ 396 PrivatePort: uint16(p), 397 Type: port.Proto(), 398 }) 399 continue 400 } 401 for _, binding := range bindings { 402 h, err := nat.ParsePort(binding.HostPort) 403 if err != nil { 404 log.G(context.TODO()).WithError(err).Warn("invalid host port map") 405 continue 406 } 407 snapshot.Ports = append(snapshot.Ports, types.Port{ 408 PrivatePort: uint16(p), 409 PublicPort: uint16(h), 410 Type: port.Proto(), 411 IP: binding.HostIP, 412 }) 413 } 414 } 415 } 416 snapshot.NetworkSettings = &types.SummaryNetworkSettings{Networks: networks} 417 418 return snapshot 419 } 420 421 // containerByIDIndexer is used to extract the ID field from Container types. 422 // memdb.StringFieldIndex can not be used since ID is a field from an embedded struct. 423 type containerByIDIndexer struct{} 424 425 // terminator is the null character, used as a terminator. 426 const terminator = "\x00" 427 428 // FromObject implements the memdb.SingleIndexer interface for Container objects 429 func (e *containerByIDIndexer) FromObject(obj interface{}) (bool, []byte, error) { 430 c, ok := obj.(*Container) 431 if !ok { 432 return false, nil, fmt.Errorf("%T is not a Container", obj) 433 } 434 // Add the null character as a terminator 435 return true, []byte(c.ID + terminator), nil 436 } 437 438 // FromArgs implements the memdb.Indexer interface 439 func (e *containerByIDIndexer) FromArgs(args ...interface{}) ([]byte, error) { 440 if len(args) != 1 { 441 return nil, fmt.Errorf("must provide only a single argument") 442 } 443 arg, ok := args[0].(string) 444 if !ok { 445 return nil, fmt.Errorf("argument must be a string: %#v", args[0]) 446 } 447 // Add the null character as a terminator 448 return []byte(arg + terminator), nil 449 } 450 451 func (e *containerByIDIndexer) PrefixFromArgs(args ...interface{}) ([]byte, error) { 452 val, err := e.FromArgs(args...) 453 if err != nil { 454 return nil, err 455 } 456 457 // Strip the null terminator, the rest is a prefix 458 return bytes.TrimSuffix(val, []byte(terminator)), nil 459 } 460 461 // namesByNameIndexer is used to index container name associations by name. 462 type namesByNameIndexer struct{} 463 464 func (e *namesByNameIndexer) FromObject(obj interface{}) (bool, []byte, error) { 465 n, ok := obj.(nameAssociation) 466 if !ok { 467 return false, nil, fmt.Errorf(`%T does not have type "nameAssociation"`, obj) 468 } 469 470 // Add the null character as a terminator 471 return true, []byte(n.name + terminator), nil 472 } 473 474 func (e *namesByNameIndexer) FromArgs(args ...interface{}) ([]byte, error) { 475 if len(args) != 1 { 476 return nil, fmt.Errorf("must provide only a single argument") 477 } 478 arg, ok := args[0].(string) 479 if !ok { 480 return nil, fmt.Errorf("argument must be a string: %#v", args[0]) 481 } 482 // Add the null character as a terminator 483 return []byte(arg + terminator), nil 484 } 485 486 // namesByContainerIDIndexer is used to index container names by container ID. 487 type namesByContainerIDIndexer struct{} 488 489 func (e *namesByContainerIDIndexer) FromObject(obj interface{}) (bool, []byte, error) { 490 n, ok := obj.(nameAssociation) 491 if !ok { 492 return false, nil, fmt.Errorf(`%T does not have type "nameAssociation"`, obj) 493 } 494 495 // Add the null character as a terminator 496 return true, []byte(n.containerID + terminator), nil 497 } 498 499 func (e *namesByContainerIDIndexer) FromArgs(args ...interface{}) ([]byte, error) { 500 if len(args) != 1 { 501 return nil, fmt.Errorf("must provide only a single argument") 502 } 503 arg, ok := args[0].(string) 504 if !ok { 505 return nil, fmt.Errorf("argument must be a string: %#v", args[0]) 506 } 507 // Add the null character as a terminator 508 return []byte(arg + terminator), nil 509 }