summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libpod/boltdb_state.go98
-rw-r--r--libpod/in_memory_state.go40
-rw-r--r--libpod/runtime_ctr.go113
-rw-r--r--libpod/state.go13
-rw-r--r--pkg/api/handlers/compat/containers.go15
-rw-r--r--pkg/api/handlers/compat/secrets.go32
-rw-r--r--pkg/api/server/register_secrets.go4
-rw-r--r--pkg/domain/entities/secrets.go23
-rw-r--r--test/apiv2/20-containers.at7
-rw-r--r--test/apiv2/50-secrets.at11
-rw-r--r--test/e2e/rename_test.go21
11 files changed, 270 insertions, 107 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go
index 6f2eaeab2..122dd080f 100644
--- a/libpod/boltdb_state.go
+++ b/libpod/boltdb_state.go
@@ -1681,6 +1681,104 @@ func (s *BoltState) RewriteContainerConfig(ctr *Container, newCfg *ContainerConf
return err
}
+// SafeRewriteContainerConfig rewrites a container's configuration in a more
+// limited fashion than RewriteContainerConfig. It is marked as safe to use
+// under most circumstances, unlike RewriteContainerConfig.
+// DO NOT USE TO: Change container dependencies, change pod membership, change
+// locks, change container ID.
+func (s *BoltState) SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error {
+ if !s.valid {
+ return define.ErrDBClosed
+ }
+
+ if !ctr.valid {
+ return define.ErrCtrRemoved
+ }
+
+ if newName != "" && newCfg.Name != newName {
+ return errors.Wrapf(define.ErrInvalidArg, "new name %s for container %s must match name in given container config", newName, ctr.ID())
+ }
+ if newName != "" && oldName == "" {
+ return errors.Wrapf(define.ErrInvalidArg, "must provide old name for container if a new name is given")
+ }
+
+ newCfgJSON, err := json.Marshal(newCfg)
+ if err != nil {
+ return errors.Wrapf(err, "error marshalling new configuration JSON for container %s", ctr.ID())
+ }
+
+ db, err := s.getDBCon()
+ if err != nil {
+ return err
+ }
+ defer s.deferredCloseDBCon(db)
+
+ err = db.Update(func(tx *bolt.Tx) error {
+ if newName != "" {
+ idBkt, err := getIDBucket(tx)
+ if err != nil {
+ return err
+ }
+ namesBkt, err := getNamesBucket(tx)
+ if err != nil {
+ return err
+ }
+ allCtrsBkt, err := getAllCtrsBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ needsRename := true
+ if exists := namesBkt.Get([]byte(newName)); exists != nil {
+ if string(exists) == ctr.ID() {
+ // Name already associated with the ID
+ // of this container. No need for a
+ // rename.
+ needsRename = false
+ } else {
+ return errors.Wrapf(define.ErrCtrExists, "name %s already in use, cannot rename container %s", newName, ctr.ID())
+ }
+ }
+
+ if needsRename {
+ // We do have to remove the old name. The other
+ // buckets are ID-indexed so we just need to
+ // overwrite the values there.
+ if err := namesBkt.Delete([]byte(oldName)); err != nil {
+ return errors.Wrapf(err, "error deleting container %s old name from DB for rename", ctr.ID())
+ }
+ if err := idBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil {
+ return errors.Wrapf(err, "error renaming container %s in ID bucket in DB", ctr.ID())
+ }
+ if err := namesBkt.Put([]byte(newName), []byte(ctr.ID())); err != nil {
+ return errors.Wrapf(err, "error adding new name %s for container %s in DB", newName, ctr.ID())
+ }
+ if err := allCtrsBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil {
+ return errors.Wrapf(err, "error renaming container %s in all containers bucket in DB", ctr.ID())
+ }
+ }
+ }
+
+ ctrBkt, err := getCtrBucket(tx)
+ if err != nil {
+ return err
+ }
+
+ ctrDB := ctrBkt.Bucket([]byte(ctr.ID()))
+ if ctrDB == nil {
+ ctr.valid = false
+ return errors.Wrapf(define.ErrNoSuchCtr, "no container with ID %s found in DB", ctr.ID())
+ }
+
+ if err := ctrDB.Put(configKey, newCfgJSON); err != nil {
+ return errors.Wrapf(err, "error updating container %s config JSON", ctr.ID())
+ }
+
+ return nil
+ })
+ return err
+}
+
// RewritePodConfig rewrites a pod's configuration.
// WARNING: This function is DANGEROUS. Do not use without reading the full
// comment on this function in state.go.
diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go
index 26f15d9c8..3875878ed 100644
--- a/libpod/in_memory_state.go
+++ b/libpod/in_memory_state.go
@@ -822,6 +822,46 @@ func (s *InMemoryState) RewriteContainerConfig(ctr *Container, newCfg *Container
return nil
}
+// SafeRewriteContainerConfig rewrites a container's configuration.
+// It's safer than RewriteContainerConfig, but still has limitations. Please
+// read the comment in state.go before using.
+func (s *InMemoryState) SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error {
+ if !ctr.valid {
+ return define.ErrCtrRemoved
+ }
+
+ if _, err := s.nameIndex.Get(newName); err == nil {
+ return errors.Wrapf(define.ErrCtrExists, "name %s is in use", newName)
+ }
+
+ // If the container does not exist, return error
+ stateCtr, ok := s.containers[ctr.ID()]
+ if !ok {
+ ctr.valid = false
+ return errors.Wrapf(define.ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID())
+ }
+
+ // Change name in registry.
+ if s.namespace != "" {
+ nsIndex, ok := s.namespaceIndexes[s.namespace]
+ if !ok {
+ return define.ErrInternal
+ }
+ nsIndex.nameIndex.Release(oldName)
+ if err := nsIndex.nameIndex.Reserve(newName, ctr.ID()); err != nil {
+ return errors.Wrapf(err, "error registering name %s", newName)
+ }
+ }
+ s.nameIndex.Release(oldName)
+ if err := s.nameIndex.Reserve(newName, ctr.ID()); err != nil {
+ return errors.Wrapf(err, "error registering name %s", newName)
+ }
+
+ stateCtr.config = newCfg
+
+ return nil
+}
+
// RewritePodConfig rewrites a pod's configuration.
// This function is DANGEROUS, even with in-memory state.
// Please read the full comment on it in state.go before using it.
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index 8bf862bf2..301c4627d 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -74,8 +74,7 @@ func (r *Runtime) RestoreContainer(ctx context.Context, rSpec *spec.Spec, config
}
// RenameContainer renames the given container.
-// The given container object will be rendered unusable, and a new, renamed
-// Container will be returned.
+// Returns a copy of the container that has been renamed if successful.
func (r *Runtime) RenameContainer(ctx context.Context, ctr *Container, newName string) (*Container, error) {
ctr.lock.Lock()
defer ctr.lock.Unlock()
@@ -88,26 +87,6 @@ func (r *Runtime) RenameContainer(ctx context.Context, ctr *Container, newName s
return nil, define.RegexError
}
- // Check if the name is available.
- // This is *100% NOT ATOMIC* so any failures in-flight will do
- // *VERY BAD THINGS* to the state. So we have to try and catch all we
- // can before starting.
- if _, err := r.state.LookupContainerID(newName); err == nil {
- return nil, errors.Wrapf(define.ErrCtrExists, "name %s is already in use by another container", newName)
- }
- if _, err := r.state.LookupPod(newName); err == nil {
- return nil, errors.Wrapf(define.ErrPodExists, "name %s is already in use by another pod", newName)
- }
-
- // TODO: Investigate if it is possible to remove this limitation.
- depCtrs, err := r.state.ContainerInUse(ctr)
- if err != nil {
- return nil, err
- }
- if len(depCtrs) > 0 {
- return nil, errors.Wrapf(define.ErrCtrExists, "cannot rename container %s as it is in use by other containers: %v", ctr.ID(), strings.Join(depCtrs, ","))
- }
-
// We need to pull an updated config, in case another rename fired and
// the config was re-written.
newConf, err := r.state.GetContainerConfig(ctr.ID())
@@ -116,95 +95,33 @@ func (r *Runtime) RenameContainer(ctx context.Context, ctr *Container, newName s
}
ctr.config = newConf
- // TODO: This is going to fail if we have active exec sessions, too.
- // Investigate fixing that at a later date.
-
- var pod *Pod
- if ctr.config.Pod != "" {
- tmpPod, err := r.state.Pod(ctr.config.Pod)
- if err != nil {
- return nil, errors.Wrapf(err, "error retrieving container %s pod", ctr.ID())
- }
- pod = tmpPod
- // Lock pod to ensure it's not removed while we're working
- pod.lock.Lock()
- defer pod.lock.Unlock()
- }
-
- // Lock all volumes to ensure they are not removed while we're working
- volsLocked := make(map[string]bool)
- for _, namedVol := range ctr.config.NamedVolumes {
- if volsLocked[namedVol.Name] {
- continue
- }
- vol, err := r.state.Volume(namedVol.Name)
- if err != nil {
- return nil, errors.Wrapf(err, "error retrieving volume used by container %s", ctr.ID())
- }
-
- volsLocked[vol.Name()] = true
- vol.lock.Lock()
- defer vol.lock.Unlock()
- }
-
logrus.Infof("Going to rename container %s from %q to %q", ctr.ID(), ctr.Name(), newName)
- // Step 1: remove the old container.
- if pod != nil {
- if err := r.state.RemoveContainerFromPod(pod, ctr); err != nil {
- return nil, errors.Wrapf(err, "error renaming container %s", ctr.ID())
- }
- } else {
- if err := r.state.RemoveContainer(ctr); err != nil {
- return nil, errors.Wrapf(err, "error renaming container %s", ctr.ID())
- }
- }
-
- // Step 2: Make a new container based on the old one.
- // TODO: Should we deep-copy the container config and state, to be safe?
- newCtr := new(Container)
- newCtr.config = ctr.config
- newCtr.state = ctr.state
- newCtr.lock = ctr.lock
- newCtr.ociRuntime = ctr.ociRuntime
- newCtr.runtime = r
- newCtr.rootlessSlirpSyncR = ctr.rootlessSlirpSyncR
- newCtr.rootlessSlirpSyncW = ctr.rootlessSlirpSyncW
- newCtr.rootlessPortSyncR = ctr.rootlessPortSyncR
- newCtr.rootlessPortSyncW = ctr.rootlessPortSyncW
-
- newCtr.valid = true
- newCtr.config.Name = newName
-
- // Step 3: Add that new container to the DB
- if pod != nil {
- if err := r.state.AddContainerToPod(pod, newCtr); err != nil {
- return nil, errors.Wrapf(err, "error renaming container %s", newCtr.ID())
- }
- } else {
- if err := r.state.AddContainer(newCtr); err != nil {
- return nil, errors.Wrapf(err, "error renaming container %s", newCtr.ID())
- }
- }
+ // Step 1: Alter the config. Save the old name, we need it to rewrite
+ // the config.
+ oldName := ctr.config.Name
+ ctr.config.Name = newName
- // Step 4: Save the new container, to force the state to be written to
- // the DB. This may not be necessary, depending on DB implementation,
- // but let's do it to be safe.
- if err := newCtr.save(); err != nil {
- return nil, err
+ // Step 2: rewrite the old container's config in the DB.
+ if err := r.state.SafeRewriteContainerConfig(ctr, oldName, ctr.config.Name, ctr.config); err != nil {
+ // Assume the rename failed.
+ // Set config back to the old name so reflect what is actually
+ // present in the DB.
+ ctr.config.Name = oldName
+ return nil, errors.Wrapf(err, "error renaming container %s", ctr.ID())
}
- // Step 5: rename the container in c/storage.
+ // Step 3: rename the container in c/storage.
// This can fail if the name is already in use by a non-Podman
// container. This puts us in a bad spot - we've already renamed the
// container in Podman. We can swap the order, but then we have the
// opposite problem. Atomicity is a real problem here, with no easy
// solution.
- if err := r.store.SetNames(newCtr.ID(), []string{newCtr.Name()}); err != nil {
+ if err := r.store.SetNames(ctr.ID(), []string{ctr.Name()}); err != nil {
return nil, err
}
- return newCtr, nil
+ return ctr, nil
}
func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConfig) (*Container, error) {
diff --git a/libpod/state.go b/libpod/state.go
index 074d21740..4b711bae9 100644
--- a/libpod/state.go
+++ b/libpod/state.go
@@ -155,6 +155,19 @@ type State interface {
// answer is this: use this only very sparingly, and only if you really
// know what you're doing.
RewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) error
+ // This is a more limited version of RewriteContainerConfig, though it
+ // comes with the added ability to alter a container's name. In exchange
+ // it loses the ability to manipulate the container's locks.
+ // It is not intended to be as restrictive as RewriteContainerConfig, in
+ // that we allow it to be run while other Podman processes are running,
+ // and without holding the alive lock.
+ // Container ID and pod membership still *ABSOLUTELY CANNOT* be altered.
+ // Also, you cannot change a container's dependencies - shared namespace
+ // containers or generic dependencies - at present. This is
+ // theoretically possible but not yet implemented.
+ // If newName is not "" the container will be renamed to the new name.
+ // The oldName parameter is only required if newName is given.
+ SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error
// PLEASE READ THE DESCRIPTION FOR RewriteContainerConfig BEFORE USING.
// This function is identical to RewriteContainerConfig, save for the
// fact that it is used with pods instead.
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 1e84888af..d26bb50f4 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -321,6 +321,19 @@ func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error
Type: portMapping.Protocol,
}
}
+ inspect, err := l.Inspect(false)
+ if err != nil {
+ return nil, err
+ }
+
+ n, err := json.Marshal(inspect.NetworkSettings)
+ if err != nil {
+ return nil, err
+ }
+ networkSettings := types.SummaryNetworkSettings{}
+ if err := json.Unmarshal(n, &networkSettings); err != nil {
+ return nil, err
+ }
return &handlers.Container{Container: types.Container{
ID: l.ID(),
@@ -339,7 +352,7 @@ func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error
NetworkMode string `json:",omitempty"`
}{
"host"},
- NetworkSettings: nil,
+ NetworkSettings: &networkSettings,
Mounts: nil,
},
ContainerCreateConfig: types.ContainerCreateConfig{},
diff --git a/pkg/api/handlers/compat/secrets.go b/pkg/api/handlers/compat/secrets.go
index c5ee8c324..86e3887a4 100644
--- a/pkg/api/handlers/compat/secrets.go
+++ b/pkg/api/handlers/compat/secrets.go
@@ -40,7 +40,21 @@ func ListSecrets(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- utils.WriteResponse(w, http.StatusOK, reports)
+ if utils.IsLibpodRequest(r) {
+ utils.WriteResponse(w, http.StatusOK, reports)
+ return
+ }
+ // Docker compat expects a version field that increments when the secret is updated
+ // We currently can't update a secret, so we default the version to 1
+ compatReports := make([]entities.SecretInfoReportCompat, 0, len(reports))
+ for _, report := range reports {
+ compatRep := entities.SecretInfoReportCompat{
+ SecretInfoReport: *report,
+ Version: entities.SecretVersion{Index: 1},
+ }
+ compatReports = append(compatReports, compatRep)
+ }
+ utils.WriteResponse(w, http.StatusOK, compatReports)
}
func InspectSecret(w http.ResponseWriter, r *http.Request) {
@@ -59,7 +73,21 @@ func InspectSecret(w http.ResponseWriter, r *http.Request) {
utils.SecretNotFound(w, name, errs[0])
return
}
- utils.WriteResponse(w, http.StatusOK, reports[0])
+ if len(reports) < 1 {
+ utils.InternalServerError(w, err)
+ return
+ }
+ if utils.IsLibpodRequest(r) {
+ utils.WriteResponse(w, http.StatusOK, reports[0])
+ return
+ }
+ // Docker compat expects a version field that increments when the secret is updated
+ // We currently can't update a secret, so we default the version to 1
+ compatReport := entities.SecretInfoReportCompat{
+ SecretInfoReport: *reports[0],
+ Version: entities.SecretVersion{Index: 1},
+ }
+ utils.WriteResponse(w, http.StatusOK, compatReport)
}
func RemoveSecret(w http.ResponseWriter, r *http.Request) {
diff --git a/pkg/api/server/register_secrets.go b/pkg/api/server/register_secrets.go
index 1c5f5954b..531623845 100644
--- a/pkg/api/server/register_secrets.go
+++ b/pkg/api/server/register_secrets.go
@@ -115,7 +115,7 @@ func (s *APIServer) registerSecretHandlers(r *mux.Router) error {
// parameters:
// responses:
// '200':
- // "$ref": "#/responses/SecretListResponse"
+ // "$ref": "#/responses/SecretListCompatResponse"
// '500':
// "$ref": "#/responses/InternalError"
r.Handle(VersionedPath("/secrets"), s.APIHandler(compat.ListSecrets)).Methods(http.MethodGet)
@@ -158,7 +158,7 @@ func (s *APIServer) registerSecretHandlers(r *mux.Router) error {
// - application/json
// responses:
// '200':
- // "$ref": "#/responses/SecretInspectResponse"
+ // "$ref": "#/responses/SecretInspectCompatResponse"
// '404':
// "$ref": "#/responses/NoSuchSecret"
// '500':
diff --git a/pkg/domain/entities/secrets.go b/pkg/domain/entities/secrets.go
index 3481cbe05..8ede981da 100644
--- a/pkg/domain/entities/secrets.go
+++ b/pkg/domain/entities/secrets.go
@@ -42,6 +42,15 @@ type SecretInfoReport struct {
Spec SecretSpec
}
+type SecretInfoReportCompat struct {
+ SecretInfoReport
+ Version SecretVersion
+}
+
+type SecretVersion struct {
+ Index int
+}
+
type SecretSpec struct {
Name string
Driver SecretDriverSpec
@@ -78,6 +87,13 @@ type SwagSecretListResponse struct {
Body []*SecretInfoReport
}
+// Secret list response
+// swagger:response SecretListCompatResponse
+type SwagSecretListCompatResponse struct {
+ // in:body
+ Body []*SecretInfoReportCompat
+}
+
// Secret inspect response
// swagger:response SecretInspectResponse
type SwagSecretInspectResponse struct {
@@ -85,6 +101,13 @@ type SwagSecretInspectResponse struct {
Body SecretInfoReport
}
+// Secret inspect compat
+// swagger:response SecretInspectCompatResponse
+type SwagSecretInspectCompatResponse struct {
+ // in:body
+ Body SecretInfoReportCompat
+}
+
// No such secret
// swagger:response NoSuchSecret
type SwagErrNoSuchSecret struct {
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index 4b40fde80..18364a47d 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -31,6 +31,13 @@ t GET libpod/containers/json?all=true 200 \
.[0].ExitCode=0 \
.[0].IsInfra=false
+# Test compat API for Network Settings
+t GET /containers/json?all=true 200 \
+ length=1 \
+ .[0].Id~[0-9a-f]\\{64\\} \
+ .[0].Image=$IMAGE \
+ .[0].NetworkSettings.Networks.podman.NetworkID=podman
+
# Make sure `limit` works.
t GET libpod/containers/json?limit=1 200 \
length=1 \
diff --git a/test/apiv2/50-secrets.at b/test/apiv2/50-secrets.at
index 69e1f3ae9..c4ffb5883 100644
--- a/test/apiv2/50-secrets.at
+++ b/test/apiv2/50-secrets.at
@@ -14,15 +14,18 @@ t POST secrets/create '"Name":"mysecret","Data":"c2VjcmV0","Labels":{"fail":"fai
t POST secrets/create '"Name":"mysecret","Data":"c2VjcmV0"' 409
# secret inspect
-t GET secrets/mysecret 200\
- .Spec.Name=mysecret
+t GET secrets/mysecret 200 \
+ .Spec.Name=mysecret \
+ .Version.Index=1
# secret inspect non-existent secret
t GET secrets/bogus 404
# secret list
-t GET secrets 200\
- length=1
+t GET secrets 200 \
+ length=1 \
+ .[0].Spec.Name=mysecret \
+ .[0].Version.Index=1
# secret list unsupported filters
t GET secrets?filters='{"name":["foo1"]}' 400
diff --git a/test/e2e/rename_test.go b/test/e2e/rename_test.go
index f19413221..14696c0f6 100644
--- a/test/e2e/rename_test.go
+++ b/test/e2e/rename_test.go
@@ -89,4 +89,25 @@ var _ = Describe("podman rename", func() {
Expect(ps.ExitCode()).To(Equal(0))
Expect(ps.OutputToString()).To(ContainSubstring(newName))
})
+
+ It("Rename a running container with exec sessions", func() {
+ ctrName := "testCtr"
+ ctr := podmanTest.Podman([]string{"run", "-d", "--name", ctrName, ALPINE, "top"})
+ ctr.WaitWithDefaultTimeout()
+ Expect(ctr.ExitCode()).To(Equal(0))
+
+ exec := podmanTest.Podman([]string{"exec", "-d", ctrName, "top"})
+ exec.WaitWithDefaultTimeout()
+ Expect(exec.ExitCode()).To(Equal(0))
+
+ newName := "aNewName"
+ rename := podmanTest.Podman([]string{"rename", ctrName, newName})
+ rename.WaitWithDefaultTimeout()
+ Expect(rename.ExitCode()).To(Equal(0))
+
+ ps := podmanTest.Podman([]string{"ps", "-aq", "--filter", fmt.Sprintf("name=%s", newName), "--format", "{{ .Names }}"})
+ ps.WaitWithDefaultTimeout()
+ Expect(ps.ExitCode()).To(Equal(0))
+ Expect(ps.OutputToString()).To(ContainSubstring(newName))
+ })
})