gopkg.in/docker/docker.v20@v20.10.27/container/view.go (about)

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