From 24457873366bbd23d71b364a63037f34c652c04a Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 25 Jun 2018 10:35:15 -0400 Subject: Add container and pod namespaces to configs Libpod namespaces are a way to logically separate groups of pods and containers within the state. Signed-off-by: Matthew Heon --- libpod/options.go | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) (limited to 'libpod/options.go') diff --git a/libpod/options.go b/libpod/options.go index 718b44930..fb07d1edf 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -388,8 +388,9 @@ func WithStdin() CtrCreateOption { } // WithPod adds the container to a pod. -// Containers which join a pod can only join the namespaces of other containers -// in the same pod. +// Containers which join a pod can only join the Linux namespaces of other +// containers in the same pod. +// Containers can only join pods in the same libpod namespace. func (r *Runtime) WithPod(pod *Pod) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { @@ -944,7 +945,8 @@ func WithCommand(command []string) CtrCreateOption { } } -// WithRootFS sets the rootfs for the container +// WithRootFS sets the rootfs for the container. +// This creates a container from a directory on disk and not an image. func WithRootFS(rootfs string) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { @@ -961,6 +963,22 @@ func WithRootFS(rootfs string) CtrCreateOption { } } +// WithNamespace sets the namespace the container will be created in. +// Namespaces are used to create separate views of Podman's state - runtimes can +// join a specific namespace and see only containers and pods in that namespace. +// Empty string namespaces are allowed, and correspond to a lack of namespace. +func WithNamespace(ns string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + ctr.config.Namespace = ns + + return nil + } +} + // Pod Creation Options // WithPodName sets the name of the pod. @@ -1025,3 +1043,20 @@ func WithPodCgroups() PodCreateOption { return nil } } + +// WithPodNamespace sets the namespace for the created pod. +// Namespaces are used to create separate views of Podman's state - runtimes can +// join a specific namespace and see only containers and pods in that namespace. +// Empty string namespaces are allowed, and correspond to a lack of namespace. +// Containers must belong to the same namespace as the pod they join. +func WithPodNamespace(ns string) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return ErrPodFinalized + } + + pod.config.Namespace = ns + + return nil + } +} -- cgit v1.2.3-54-g00ecf From e838dcb4bf7dc35b1bcf21edad6a1f6c59d969ab Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 25 Jun 2018 23:39:11 -0400 Subject: Add constraint that dependencies must be in the same ns Dependency containers must be in the same namespace, to ensure there are never problems resolving a dependency. Signed-off-by: Matthew Heon --- libpod/boltdb_state.go | 112 ++++++++++++++++++++++++++++++++---- libpod/boltdb_state_internal.go | 50 +++++++++++++++- libpod/boltdb_state_linux.go | 8 +++ libpod/in_memory_state.go | 19 ++++++- libpod/options.go | 25 +++++++- libpod/runtime.go | 16 +++++- libpod/state.go | 4 ++ libpod/state_test.go | 123 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 340 insertions(+), 17 deletions(-) (limited to 'libpod/options.go') diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 648c14267..73a906761 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -1,6 +1,7 @@ package libpod import ( + "bytes" "encoding/json" "os" "strings" @@ -11,10 +12,12 @@ import ( // BoltState is a state implementation backed by a Bolt DB type BoltState struct { - valid bool - dbPath string - lockDir string - runtime *Runtime + valid bool + dbPath string + namespace string + namespaceBytes []byte + lockDir string + runtime *Runtime } // NewBoltState creates a new bolt-backed state database @@ -23,6 +26,8 @@ func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) { state.dbPath = path state.lockDir = lockDir state.runtime = runtime + state.namespace = "" + state.namespaceBytes = nil // Make the directory that will hold container lockfiles if err := os.MkdirAll(lockDir, 0750); err != nil { @@ -47,6 +52,9 @@ func NewBoltState(path, lockDir string, runtime *Runtime) (State, error) { if _, err := tx.CreateBucketIfNotExists(nameRegistryBkt); err != nil { return errors.Wrapf(err, "error creating name-registry bucket") } + if _, err := tx.CreateBucketIfNotExists(nsRegistryBkt); err != nil { + return errors.Wrapf(err, "error creating ns-registry bucket") + } if _, err := tx.CreateBucketIfNotExists(ctrBkt); err != nil { return errors.Wrapf(err, "error creating containers bucket") } @@ -193,6 +201,20 @@ func (s *BoltState) Refresh() error { return err } +// SetNamespace sets the namespace that will be used for container and pod +// retrieval +func (s *BoltState) SetNamespace(ns string) error { + s.namespace = ns + + if ns != "" { + s.namespaceBytes = []byte(ns) + } else { + s.namespaceBytes = nil + } + + return nil +} + // Container retrieves a single container from the state by its full ID func (s *BoltState) Container(id string) (*Container, error) { if id == "" { @@ -262,6 +284,11 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { return err } + nsBucket, err := getNSBucket(tx) + if err != nil { + return err + } + // First, check if the ID given was the actual container ID var id []byte ctrExists := ctrBucket.Bucket([]byte(idOrName)) @@ -274,6 +301,14 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { // Use else-if in case the name is set to a partial ID exists := false err = idBucket.ForEach(func(checkID, checkName []byte) error { + // If the container isn't in our namespace, we + // can't match it + if s.namespaceBytes != nil { + ns := nsBucket.Get(checkID) + if !bytes.Equal(ns, s.namespaceBytes) { + return nil + } + } if string(checkName) == idOrName { if exists { return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName) @@ -334,7 +369,14 @@ func (s *BoltState) HasContainer(id string) (bool, error) { ctrExists := ctrBucket.Bucket(ctrID) if ctrExists != nil { - exists = true + if s.namespaceBytes != nil { + nsBytes := ctrBucket.Get(namespaceKey) + if bytes.Equal(nsBytes, nsBytes) { + exists = true + } + } else { + exists = true + } } return nil @@ -383,7 +425,7 @@ func (s *BoltState) RemoveContainer(ctr *Container) error { defer db.Close() err = db.Update(func(tx *bolt.Tx) error { - return removeContainer(ctr, nil, tx) + return removeContainer(ctr, nil, tx, s.namespace) }) return err } @@ -398,6 +440,12 @@ func (s *BoltState) UpdateContainer(ctr *Container) error { return ErrCtrRemoved } + if s.namespace != "" { + if s.namespace != ctr.config.Namespace { + return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + } + } + newState := new(containerState) netNSPath := "" @@ -460,6 +508,12 @@ func (s *BoltState) SaveContainer(ctr *Container) error { return ErrCtrRemoved } + if s.namespace != "" { + if s.namespace != ctr.config.Namespace { + return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + } + } + stateJSON, err := json.Marshal(ctr.state) if err != nil { return errors.Wrapf(err, "error marshalling container %s state to JSON", ctr.ID()) @@ -519,6 +573,12 @@ func (s *BoltState) ContainerInUse(ctr *Container) ([]string, error) { return nil, ErrCtrRemoved } + if s.namespace != "" { + if s.namespace != ctr.config.Namespace { + return nil, errors.Wrapf(ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + } + } + depCtrs := []string{} db, err := s.getDBCon() @@ -602,9 +662,20 @@ func (s *BoltState) AllContainers() ([]*Container, error) { ctr.config = new(ContainerConfig) ctr.state = new(containerState) - ctrs = append(ctrs, ctr) + if err := s.getContainerFromDB(id, ctr, ctrBucket); err != nil { + // If the error is a namespace mismatch, we can + // ignore it safely. + // We just won't include the container in the + // results. + if errors.Cause(err) != ErrNSMismatch { + return err + } + } else { + ctrs = append(ctrs, ctr) + } + + return nil - return s.getContainerFromDB(id, ctr, ctrBucket) }) }) if err != nil { @@ -682,6 +753,11 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { return err } + nsBkt, err := getNSBucket(tx) + if err != nil { + return err + } + // First, check if the ID given was the actual pod ID var id []byte podExists := podBkt.Bucket([]byte(idOrName)) @@ -694,6 +770,14 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { // Use else-if in case the name is set to a partial ID exists := false err = idBucket.ForEach(func(checkID, checkName []byte) error { + // If the pod isn't in our namespace, we + // can't match it + if s.namespaceBytes != nil { + ns := nsBkt.Get(checkID) + if !bytes.Equal(ns, s.namespaceBytes) { + return nil + } + } if string(checkName) == idOrName { if exists { return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName) @@ -719,7 +803,6 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { // We might have found a container ID, but it's OK // We'll just fail in getPodFromDB with ErrNoSuchPod - return s.getPodFromDB(id, pod, podBkt) }) if err != nil { @@ -757,7 +840,14 @@ func (s *BoltState) HasPod(id string) (bool, error) { podDB := podBkt.Bucket(podID) if podDB != nil { - exists = true + if s.namespaceBytes != nil { + podNS := podDB.Get(namespaceKey) + if bytes.Equal(s.namespaceBytes, podNS) { + exists = true + } + } else { + exists = true + } } return nil @@ -1318,7 +1408,7 @@ func (s *BoltState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { defer db.Close() err = db.Update(func(tx *bolt.Tx) error { - return removeContainer(ctr, pod, tx) + return removeContainer(ctr, pod, tx, s.namespace) }) return err } diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index 5661c5b7f..718c43046 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -16,6 +16,7 @@ import ( const ( idRegistryName = "id-registry" nameRegistryName = "name-registry" + nsRegistryName = "ns-registry" ctrName = "ctr" allCtrsName = "all-ctrs" podName = "pod" @@ -34,6 +35,7 @@ const ( var ( idRegistryBkt = []byte(idRegistryName) nameRegistryBkt = []byte(nameRegistryName) + nsRegistryBkt = []byte(nsRegistryName) ctrBkt = []byte(ctrName) allCtrsBkt = []byte(allCtrsName) podBkt = []byte(podName) @@ -168,6 +170,14 @@ func getNamesBucket(tx *bolt.Tx) (*bolt.Bucket, error) { return bkt, nil } +func getNSBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(nsRegistryBkt) + if bkt == nil { + return nil, errors.Wrapf(ErrDBBadConfig, "namespace registry bucket not found in DB") + } + return bkt, nil +} + func getCtrBucket(tx *bolt.Tx) (*bolt.Bucket, error) { bkt := tx.Bucket(ctrBkt) if bkt == nil { @@ -214,6 +224,13 @@ func (s *BoltState) getPodFromDB(id []byte, pod *Pod, podBkt *bolt.Bucket) error return errors.Wrapf(ErrNoSuchPod, "pod with ID %s not found", string(id)) } + if s.namespaceBytes != nil { + podNamespaceBytes := podDB.Get(namespaceKey) + if !bytes.Equal(s.namespaceBytes, podNamespaceBytes) { + return errors.Wrapf(ErrNSMismatch, "cannot retrieve pod %s as it is part of namespace %q and we are in namespace %q", string(id), string(podNamespaceBytes), s.namespace) + } + } + podConfigBytes := podDB.Get(configKey) if podConfigBytes == nil { return errors.Wrapf(ErrInternal, "pod %s is missing configuration key in DB", string(id)) @@ -287,6 +304,11 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { return err } + nsBucket, err := getNSBucket(tx) + if err != nil { + return err + } + ctrBucket, err := getCtrBucket(tx) if err != nil { return err @@ -343,6 +365,11 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { if err := namesBucket.Put(ctrName, ctrID); err != nil { return errors.Wrapf(err, "error adding container %s name (%s) to DB", ctr.ID(), ctr.Name()) } + if ctrNamespace != nil { + if err := nsBucket.Put(ctrID, ctrNamespace); err != nil { + return errors.Wrapf(err, "error adding container %s namespace (%q) to DB", ctr.ID(), ctr.Namespace()) + } + } if err := allCtrsBucket.Put(ctrID, ctrName); err != nil { return errors.Wrapf(err, "error adding container %s to all containers bucket in DB", ctr.ID()) } @@ -403,6 +430,11 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { } } + depNamespace := depCtrBkt.Get(namespaceKey) + if !bytes.Equal(ctrNamespace, depNamespace) { + return errors.Wrapf(ErrNSMismatch, "container %s in namespace %q depends on container %s in namespace %q - namespaces must match", ctr.ID(), ctr.config.Namespace, dependsCtr, string(depNamespace)) + } + depCtrDependsBkt := depCtrBkt.Bucket(dependenciesBkt) if depCtrDependsBkt == nil { return errors.Wrapf(ErrInternal, "container %s does not have a dependencies bucket", dependsCtr) @@ -427,7 +459,7 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { // Remove a container from the DB // If pod is not nil, the container is treated as belonging to a pod, and // will be removed from the pod as well -func removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error { +func removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx, namespace string) error { ctrID := []byte(ctr.ID()) ctrName := []byte(ctr.Name()) @@ -446,6 +478,11 @@ func removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error { return err } + nsBucket, err := getNSBucket(tx) + if err != nil { + return err + } + allCtrsBucket, err := getAllCtrsBucket(tx) if err != nil { return err @@ -475,6 +512,14 @@ func removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error { return errors.Wrapf(ErrNoSuchCtr, "no container with ID %s found in DB", ctr.ID()) } + // Compare namespace + // We can't remove containers not in our namespace + if namespace != "" { + if namespace != ctr.config.Namespace { + return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, namespace) + } + } + if podDB != nil { // Check if the container is in the pod, remove it if it is podCtrs := podDB.Bucket(containersBkt) @@ -521,6 +566,9 @@ func removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error { if err := namesBucket.Delete(ctrName); err != nil { return errors.Wrapf(err, "error deleting container %s name in DB", ctr.ID()) } + if err := nsBucket.Delete(ctrID); err != nil { + return errors.Wrapf(err, "error deleting container %s namespace in DB", ctr.ID()) + } if err := allCtrsBucket.Delete(ctrID); err != nil { return errors.Wrapf(err, "error deleting container %s from all containers bucket in DB", ctr.ID()) } diff --git a/libpod/boltdb_state_linux.go b/libpod/boltdb_state_linux.go index ceea955bd..7c0e08fb3 100644 --- a/libpod/boltdb_state_linux.go +++ b/libpod/boltdb_state_linux.go @@ -3,6 +3,7 @@ package libpod import ( + "bytes" "encoding/json" "path/filepath" @@ -19,6 +20,13 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt. return errors.Wrapf(ErrNoSuchCtr, "container %s not found in DB", string(id)) } + if s.namespaceBytes != nil { + ctrNamespaceBytes := ctrBkt.Get(namespaceKey) + if !bytes.Equal(s.namespaceBytes, ctrNamespaceBytes) { + return errors.Wrapf(ErrNSMismatch, "cannot retrieve container %s as it is part of namespace %q and we are in namespace %q", string(id), string(ctrNamespaceBytes), s.namespace) + } + } + configBytes := ctrBkt.Get(configKey) if configBytes == nil { return errors.Wrapf(ErrInternal, "container %s missing config key in DB", string(id)) diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index cf2f43477..4ea8740fb 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -20,6 +20,7 @@ type InMemoryState struct { podContainers map[string]map[string]*Container nameIndex *registrar.Registrar idIndex *truncindex.TruncIndex + namespace string } // NewInMemoryState initializes a new, empty in-memory state @@ -36,6 +37,8 @@ func NewInMemoryState() (State, error) { state.nameIndex = registrar.NewRegistrar() state.idIndex = truncindex.NewTruncIndex([]string{}) + state.namespace = "" + return state, nil } @@ -51,6 +54,13 @@ func (s *InMemoryState) Refresh() error { return nil } +// SetNamespace sets the namespace for container and pod retrieval. +func (s *InMemoryState) SetNamespace(ns string) error { + s.namespace = ns + + return nil +} + // Container retrieves a container from its full ID func (s *InMemoryState) Container(id string) (*Container, error) { if id == "" { @@ -133,6 +143,9 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { } else if depCtr.config.Pod != "" { return errors.Wrapf(ErrInvalidArg, "cannot depend on container in a pod if not part of same pod") } + if depCtr.config.Namespace != ctr.config.Namespace { + return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %s and cannot depend on container %s in namespace %s", ctr.ID(), ctr.config.Namespace, depID, depCtr.config.Namespace) + } } if err := s.nameIndex.Reserve(ctr.Name(), ctr.ID()); err != nil { @@ -519,9 +532,13 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { if _, ok = s.containers[depCtr]; !ok { return errors.Wrapf(ErrNoSuchCtr, "cannot depend on nonexistent container %s", depCtr) } - if _, ok = podCtrs[depCtr]; !ok { + depCtrStruct, ok := podCtrs[depCtr] + if !ok { return errors.Wrapf(ErrInvalidArg, "cannot depend on container %s as it is not in pod %s", depCtr, pod.ID()) } + if depCtrStruct.config.Namespace != ctr.config.Namespace { + return errors.Wrapf(ErrNSMismatch, "container %s is in namespace %s and cannot depend on container %s in namespace %s", ctr.ID(), ctr.config.Namespace, depCtr, depCtrStruct.config.Namespace) + } } // Add container to state diff --git a/libpod/options.go b/libpod/options.go index fb07d1edf..155c15333 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -284,6 +284,27 @@ func WithCNIPluginDir(dir string) RuntimeOption { } } +// WithNamespace sets the namespace for libpod. +// Namespace is the libpod namespace to use. +// Namespaces are used to create scopes to separate containers and pods +// in the state. +// When namespace is set, libpod will only view containers and pods in +// the same namespace. All containers and pods created will default to +// the namespace set here. +// A namespace of "", the empty string, is equivalent to no namespace, +// and all containers and pods will be visible. +func WithNamespace(ns string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return ErrRuntimeFinalized + } + + rt.config.Namespace = ns + + return nil + } +} + // Container Creation Options // WithShmDir sets the directory that should be mounted on /dev/shm. @@ -963,11 +984,11 @@ func WithRootFS(rootfs string) CtrCreateOption { } } -// WithNamespace sets the namespace the container will be created in. +// WithCtrNamespace sets the namespace the container will be created in. // Namespaces are used to create separate views of Podman's state - runtimes can // join a specific namespace and see only containers and pods in that namespace. // Empty string namespaces are allowed, and correspond to a lack of namespace. -func WithNamespace(ns string) CtrCreateOption { +func WithCtrNamespace(ns string) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { return ErrCtrFinalized diff --git a/libpod/runtime.go b/libpod/runtime.go index a551c9134..a0b673d81 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -136,10 +136,22 @@ type RuntimeConfig struct { // CNIDefaultNetwork is the network name of the default CNI network // to attach pods to CNIDefaultNetwork string `toml:"cni_default_network,omitempty"` - // HooksDirNotExistFatal switches between fatal errors and non-fatal warnings if the configured HooksDir does not exist. + // HooksDirNotExistFatal switches between fatal errors and non-fatal + // warnings if the configured HooksDir does not exist. HooksDirNotExistFatal bool `toml:"hooks_dir_not_exist_fatal"` - // DefaultMountsFile is the path to the default mounts file for testing purposes only + // DefaultMountsFile is the path to the default mounts file for testing + // purposes only DefaultMountsFile string `toml:"-"` + // Namespace is the libpod namespace to use. + // Namespaces are used to create scopes to separate containers and pods + // in the state. + // When namespace is set, libpod will only view containers and pods in + // the same namespace. All containers and pods created will default to + // the namespace set here. + // A namespace of "", the empty string, is equivalent to no namespace, + // and all containers and pods will be visible. + // The default namespace is "". + Namespace string `toml:"namespace,omitempty"` } var ( diff --git a/libpod/state.go b/libpod/state.go index b71f811ea..6239e399a 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -9,6 +9,10 @@ type State interface { // Refresh clears container and pod states after a reboot Refresh() error + // SetNamespace() sets the namespace for the store, and will determine + // what containers are retrieved with container and pod retrieval calls + SetNamespace(ns string) error + // Return a container from the database from its full ID Container(id string) (*Container, error) // Return a container from the database by full or partial ID or full diff --git a/libpod/state_test.go b/libpod/state_test.go index 7174bbf2a..e5c5a1de0 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -281,6 +281,56 @@ func TestAddCtrDepInPodFails(t *testing.T) { }) } +func TestAddCtrDepInSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestCtr1(lockPath) + assert.NoError(t, err) + testCtr2, err := getTestCtr2(lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + + testCtr1.config.Namespace = "test1" + testCtr2.config.Namespace = "test1" + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 2, len(ctrs)) + }) +} + +func TestAddCtrDepInDifferentNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestCtr1(lockPath) + assert.NoError(t, err) + testCtr2, err := getTestCtr2(lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + + testCtr1.config.Namespace = "test1" + testCtr2.config.Namespace = "test2" + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.Error(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs)) + + testContainersEqual(t, testCtr1, ctrs[0]) + }) +} + func TestGetNonexistentContainerFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.Container("does not exist") @@ -2224,6 +2274,79 @@ func TestAddContainerToPodDependencyOutsidePodFails(t *testing.T) { }) } +func TestAddContainerToPodDependencyInSameNamespaceSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + testPod.config.Namespace = "test1" + + testCtr1, err := getTestCtr2(lockPath) + assert.NoError(t, err) + testCtr1.config.Pod = testPod.ID() + testCtr1.config.Namespace = "test1" + + testCtr2, err := getTestCtrN("3", lockPath) + assert.NoError(t, err) + testCtr2.config.Pod = testPod.ID() + testCtr2.config.IPCNsCtr = testCtr1.ID() + testCtr2.config.Namespace = "test1" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr1) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr2) + assert.NoError(t, err) + + deps, err := state.ContainerInUse(testCtr1) + assert.NoError(t, err) + assert.Equal(t, 1, len(deps)) + assert.Equal(t, testCtr2.ID(), deps[0]) + }) +} + +func TestAddContainerToPodDependencyInSeparateNamespaceFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testPod, err := getTestPod1(lockPath) + assert.NoError(t, err) + testPod.config.Namespace = "test1" + + testCtr1, err := getTestCtr2(lockPath) + assert.NoError(t, err) + testCtr1.config.Pod = testPod.ID() + testCtr1.config.Namespace = "test1" + + testCtr2, err := getTestCtrN("3", lockPath) + assert.NoError(t, err) + testCtr2.config.Pod = testPod.ID() + testCtr2.config.IPCNsCtr = testCtr1.ID() + testCtr2.config.Namespace = "test2" + + err = state.AddPod(testPod) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr1) + assert.NoError(t, err) + + err = state.AddContainerToPod(testPod, testCtr2) + assert.Error(t, err) + + ctrs, err := state.PodContainers(testPod) + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs)) + + allCtrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(allCtrs)) + + deps, err := state.ContainerInUse(testCtr1) + assert.NoError(t, err) + assert.Equal(t, 0, len(deps)) + }) +} + func TestAddContainerToPodSameNamespaceSucceeds(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testPod, err := getTestPod1(lockPath) -- cgit v1.2.3-54-g00ecf From 3ae0c80806b68f712756fd660d06449e71eb41b7 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 19 Jul 2018 14:24:22 -0400 Subject: Add --namespace flag to Podman Allows joining libpod to a specific namespace when running a Podman command. Signed-off-by: Matthew Heon --- cmd/podman/libpodruntime/runtime.go | 4 ++++ cmd/podman/main.go | 5 +++++ completions/bash/podman | 1 + docs/podman.1.md | 5 +++++ libpod/options.go | 1 - 5 files changed, 15 insertions(+), 1 deletion(-) (limited to 'libpod/options.go') diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index 3216d288b..9d1347cc5 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -88,6 +88,10 @@ func GetRuntimeWithStorageOpts(c *cli.Context, storageOpts *storage.StoreOptions // TODO CLI flags for image config? // TODO CLI flag for signature policy? + if c.GlobalIsSet("namespace") { + options = append(options, libpod.WithNamespace(c.GlobalString("namespace"))) + } + if c.GlobalIsSet("runtime") { options = append(options, libpod.WithOCIRuntime(c.GlobalString("runtime"))) } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 3dbf196c2..9ae45e056 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -172,6 +172,11 @@ func main() { Usage: "log messages above specified level: debug, info, warn, error (default), fatal or panic", Value: "error", }, + cli.StringFlag{ + Name: "namespace", + Usage: "set the libpod namespace, used create separate views of the containers and pods on the system", + Value: "", + }, cli.StringFlag{ Name: "root", Usage: "path to the root directory in which data, including images, is stored", diff --git a/completions/bash/podman b/completions/bash/podman index 11203e52d..5d20bab3e 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2224,6 +2224,7 @@ _podman_podman() { --storage-driver --storage-opt --log-level + --namespace " local boolean_options=" --help -h diff --git a/docs/podman.1.md b/docs/podman.1.md index 5581e0569..ffc2669a4 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -39,6 +39,11 @@ Path to where the cpu performance results should be written log messages above specified level: debug, info, warn, error (default), fatal or panic +**--namespace** + +set namespace libpod namespace. Namespaces are used to separate groups of containers and pods in libpod's state. +When namespace is set, created containers and pods will join the given namespace, and only containers and pods in the given namespace will be visible to Podman. + **--root**=**value** Path to the root directory in which data, including images, is stored diff --git a/libpod/options.go b/libpod/options.go index 155c15333..7bb4a3632 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -285,7 +285,6 @@ func WithCNIPluginDir(dir string) RuntimeOption { } // WithNamespace sets the namespace for libpod. -// Namespace is the libpod namespace to use. // Namespaces are used to create scopes to separate containers and pods // in the state. // When namespace is set, libpod will only view containers and pods in -- cgit v1.2.3-54-g00ecf