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  }