summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/containers_create.go9
-rw-r--r--pkg/api/handlers/compat/images.go42
-rw-r--r--pkg/api/handlers/libpod/pods.go6
-rw-r--r--pkg/api/server/register_images.go25
-rw-r--r--pkg/domain/entities/play.go2
-rw-r--r--pkg/domain/infra/abi/containers.go6
-rw-r--r--pkg/domain/infra/abi/play.go89
-rw-r--r--pkg/domain/infra/abi/play_test.go254
-rw-r--r--pkg/domain/infra/abi/pods.go6
-rw-r--r--pkg/parallel/ctr/ctr.go (renamed from pkg/parallel/parallel_linux.go)45
-rw-r--r--pkg/parallel/parallel.go30
-rw-r--r--pkg/varlinkapi/pods.go7
12 files changed, 472 insertions, 49 deletions
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index a24dbaa47..4ce31cc83 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -14,8 +14,10 @@ import (
"github.com/containers/podman/v2/pkg/api/handlers"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
"github.com/containers/podman/v2/pkg/namespaces"
+ "github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/podman/v2/pkg/signal"
createconfig "github.com/containers/podman/v2/pkg/spec"
+ "github.com/containers/podman/v2/pkg/specgen"
"github.com/containers/storage"
"github.com/gorilla/schema"
"github.com/pkg/errors"
@@ -134,6 +136,11 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input
Sysctl: input.HostConfig.Sysctls,
}
+ var netmode namespaces.NetworkMode
+ if rootless.IsRootless() {
+ netmode = namespaces.NetworkMode(specgen.Slirp)
+ }
+
network := createconfig.NetworkConfig{
DNSOpt: input.HostConfig.DNSOptions,
DNSSearch: input.HostConfig.DNSSearch,
@@ -144,7 +151,7 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input
IPAddress: "",
LinkLocalIP: nil, // docker-only
MacAddress: input.MacAddress,
- // NetMode: nil,
+ NetMode: netmode,
Network: input.HostConfig.NetworkMode.NetworkName(),
NetworkAlias: nil, // docker-only now
PortBindings: input.HostConfig.PortBindings,
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go
index 9d8bc497a..f49ce59da 100644
--- a/pkg/api/handlers/compat/images.go
+++ b/pkg/api/handlers/compat/images.go
@@ -55,6 +55,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
return
}
+ defer os.Remove(tmpfile.Name())
if err := tmpfile.Close(); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
return
@@ -69,7 +70,6 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
return
}
defer rdr.Close()
- defer os.Remove(tmpfile.Name())
utils.WriteResponse(w, http.StatusOK, rdr)
}
@@ -398,3 +398,43 @@ func LoadImages(w http.ResponseWriter, r *http.Request) {
Stream: fmt.Sprintf("Loaded image: %s\n", id),
})
}
+
+func ExportImages(w http.ResponseWriter, r *http.Request) {
+ // 200 OK
+ // 500 Error
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ query := struct {
+ Names string `schema:"names"`
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+ images := make([]string, 0)
+ images = append(images, strings.Split(query.Names, ",")...)
+ tmpfile, err := ioutil.TempFile("", "api.tar")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ defer os.Remove(tmpfile.Name())
+ if err := tmpfile.Close(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
+ return
+ }
+ if err := runtime.ImageRuntime().SaveImages(r.Context(), images, "docker-archive", tmpfile.Name(), false); err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ rdr, err := os.Open(tmpfile.Name())
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile"))
+ return
+ }
+ defer rdr.Close()
+ utils.WriteResponse(w, http.StatusOK, rdr)
+}
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index 3aa554171..5422411cf 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -270,7 +270,7 @@ func PodPause(w http.ResponseWriter, r *http.Request) {
utils.PodNotFound(w, name, err)
return
}
- responses, err := pod.Pause()
+ responses, err := pod.Pause(r.Context())
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
@@ -294,7 +294,7 @@ func PodUnpause(w http.ResponseWriter, r *http.Request) {
utils.PodNotFound(w, name, err)
return
}
- responses, err := pod.Unpause()
+ responses, err := pod.Unpause(r.Context())
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
utils.Error(w, "failed to pause pod", http.StatusInternalServerError, err)
return
@@ -402,7 +402,7 @@ func PodKill(w http.ResponseWriter, r *http.Request) {
return
}
- responses, err := pod.Kill(uint(sig))
+ responses, err := pod.Kill(r.Context(), uint(sig))
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
utils.Error(w, "failed to kill pod", http.StatusInternalServerError, err)
return
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index cb0d26d1e..ad779203d 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -275,6 +275,31 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
r.Handle(VersionedPath("/images/{name:.*}/get"), s.APIHandler(compat.ExportImage)).Methods(http.MethodGet)
// Added non version path to URI to support docker non versioned paths
r.Handle("/images/{name:.*}/get", s.APIHandler(compat.ExportImage)).Methods(http.MethodGet)
+ // swagger:operation GET /images/get compat get
+ // ---
+ // tags:
+ // - images (compat)
+ // summary: Export several images
+ // description: Get a tarball containing all images and metadata for several image repositories
+ // parameters:
+ // - in: query
+ // name: names
+ // type: string
+ // required: true
+ // description: one or more image names or IDs comma separated
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // description: no error
+ // schema:
+ // type: string
+ // format: binary
+ // 500:
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/images/get"), s.APIHandler(compat.ExportImages)).Methods(http.MethodGet)
+ // Added non version path to URI to support docker non versioned paths
+ r.Handle("/images/get", s.APIHandler(compat.ExportImages)).Methods(http.MethodGet)
// swagger:operation GET /images/{name:.*}/history compat imageHistory
// ---
// tags:
diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go
index 2ba369b83..356e6869d 100644
--- a/pkg/domain/entities/play.go
+++ b/pkg/domain/entities/play.go
@@ -24,6 +24,8 @@ type PlayKubeOptions struct {
// SeccompProfileRoot - path to a directory containing seccomp
// profiles.
SeccompProfileRoot string
+ // ConfigMaps - slice of pathnames to kubernetes configmap YAMLs.
+ ConfigMaps []string
}
// PlayKubePod represents a single pod and associated containers created by play kube
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index 71b2fa8e2..ac7523094 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -23,7 +23,7 @@ import (
"github.com/containers/podman/v2/pkg/checkpoint"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi/terminal"
- "github.com/containers/podman/v2/pkg/parallel"
+ parallelctr "github.com/containers/podman/v2/pkg/parallel/ctr"
"github.com/containers/podman/v2/pkg/ps"
"github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/podman/v2/pkg/signal"
@@ -157,7 +157,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin
if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
return nil, err
}
- errMap, err := parallel.ContainerOp(ctx, ctrs, func(c *libpod.Container) error {
+ errMap, err := parallelctr.ContainerOp(ctx, ctrs, func(c *libpod.Container) error {
var err error
if options.Timeout != nil {
err = c.StopWithTimeout(*options.Timeout)
@@ -327,7 +327,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string,
return reports, nil
}
- errMap, err := parallel.ContainerOp(ctx, ctrs, func(c *libpod.Container) error {
+ errMap, err := parallelctr.ContainerOp(ctx, ctrs, func(c *libpod.Container) error {
err := ic.Libpod.RemoveContainer(ctx, c, options.Force, options.Volumes)
if err != nil {
if options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr {
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index 40edc1ae3..2de98d8f5 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -311,6 +311,22 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
ctrRestartPolicy = libpod.RestartPolicyAlways
}
+ configMaps := []v1.ConfigMap{}
+ for _, p := range options.ConfigMaps {
+ f, err := os.Open(p)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ cm, err := readConfigMapFromFile(f)
+ if err != nil {
+ return nil, errors.Wrapf(err, "%q", p)
+ }
+
+ configMaps = append(configMaps, cm)
+ }
+
containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers))
for _, container := range podYAML.Spec.Containers {
pullPolicy := util.PullImageMissing
@@ -334,7 +350,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
if err != nil {
return nil, err
}
- conf, err := kubeContainerToCreateConfig(ctx, container, newImage, namespaces, volumes, pod.ID(), podName, podInfraID, seccompPaths)
+ conf, err := kubeContainerToCreateConfig(ctx, container, newImage, namespaces, volumes, pod.ID(), podName, podInfraID, configMaps, seccompPaths)
if err != nil {
return nil, err
}
@@ -447,7 +463,7 @@ func setupSecurityContext(securityConfig *createconfig.SecurityConfig, userConfi
}
// kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container
-func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, podName, infraID string, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) {
+func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, podName, infraID string, configMaps []v1.ConfigMap, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) {
var (
containerConfig createconfig.CreateConfig
pidConfig createconfig.PidConfig
@@ -572,8 +588,17 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
}
envs = imageEnv
}
- for _, e := range containerYAML.Env {
- envs[e.Name] = e.Value
+ for _, env := range containerYAML.Env {
+ value := envVarValue(env, configMaps)
+
+ envs[env.Name] = value
+ }
+ for _, envFrom := range containerYAML.EnvFrom {
+ cmEnvs := envVarsFromConfigMap(envFrom, configMaps)
+
+ for k, v := range cmEnvs {
+ envs[k] = v
+ }
}
containerConfig.Env = envs
@@ -594,6 +619,62 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container
return &containerConfig, nil
}
+// readConfigMapFromFile returns a kubernetes configMap obtained from --configmap flag
+func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) {
+ var cm v1.ConfigMap
+
+ content, err := ioutil.ReadAll(r)
+ if err != nil {
+ return cm, errors.Wrapf(err, "unable to read ConfigMap YAML content")
+ }
+
+ if err := yaml.Unmarshal(content, &cm); err != nil {
+ return cm, errors.Wrapf(err, "unable to read YAML as Kube ConfigMap")
+ }
+
+ if cm.Kind != "ConfigMap" {
+ return cm, errors.Errorf("invalid YAML kind: %q. [ConfigMap] is the only supported by --configmap", cm.Kind)
+ }
+
+ return cm, nil
+}
+
+// envVarsFromConfigMap returns all key-value pairs as env vars from a configMap that matches the envFrom setting of a container
+func envVarsFromConfigMap(envFrom v1.EnvFromSource, configMaps []v1.ConfigMap) map[string]string {
+ envs := map[string]string{}
+
+ if envFrom.ConfigMapRef != nil {
+ cmName := envFrom.ConfigMapRef.Name
+
+ for _, c := range configMaps {
+ if cmName == c.Name {
+ envs = c.Data
+ break
+ }
+ }
+ }
+
+ return envs
+}
+
+// envVarValue returns the environment variable value configured within the container's env setting.
+// It gets the value from a configMap if specified, otherwise returns env.Value
+func envVarValue(env v1.EnvVar, configMaps []v1.ConfigMap) string {
+ for _, c := range configMaps {
+ if env.ValueFrom != nil {
+ if env.ValueFrom.ConfigMapKeyRef != nil {
+ if env.ValueFrom.ConfigMapKeyRef.Name == c.Name {
+ if value, ok := c.Data[env.ValueFrom.ConfigMapKeyRef.Key]; ok {
+ return value
+ }
+ }
+ }
+ }
+ }
+
+ return env.Value
+}
+
// kubeSeccompPaths holds information about a pod YAML's seccomp configuration
// it holds both container and pod seccomp paths
type kubeSeccompPaths struct {
diff --git a/pkg/domain/infra/abi/play_test.go b/pkg/domain/infra/abi/play_test.go
new file mode 100644
index 000000000..5595476c3
--- /dev/null
+++ b/pkg/domain/infra/abi/play_test.go
@@ -0,0 +1,254 @@
+package abi
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ v1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var configMapList = []v1.ConfigMap{
+ {
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ConfigMap",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "bar",
+ },
+ Data: map[string]string{
+ "myvar": "bar",
+ },
+ },
+ {
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ConfigMap",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foo",
+ },
+ Data: map[string]string{
+ "myvar": "foo",
+ },
+ },
+}
+
+func TestReadConfigMapFromFile(t *testing.T) {
+ tests := []struct {
+ name string
+ configMapContent string
+ expectError bool
+ expectedErrorMsg string
+ expected v1.ConfigMap
+ }{
+ {
+ "ValidConfigMap",
+ `
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: foo
+data:
+ myvar: foo
+`,
+ false,
+ "",
+ v1.ConfigMap{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "ConfigMap",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foo",
+ },
+ Data: map[string]string{
+ "myvar": "foo",
+ },
+ },
+ },
+ {
+ "InvalidYAML",
+ `
+Invalid YAML
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: foo
+data:
+ myvar: foo
+`,
+ true,
+ "unable to read YAML as Kube ConfigMap",
+ v1.ConfigMap{},
+ },
+ {
+ "InvalidKind",
+ `
+apiVersion: v1
+kind: InvalidKind
+metadata:
+ name: foo
+data:
+ myvar: foo
+`,
+ true,
+ "invalid YAML kind",
+ v1.ConfigMap{},
+ },
+ }
+
+ for _, test := range tests {
+ test := test
+ t.Run(test.name, func(t *testing.T) {
+ buf := bytes.NewBufferString(test.configMapContent)
+ cm, err := readConfigMapFromFile(buf)
+
+ if test.expectError {
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), test.expectedErrorMsg)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, test.expected, cm)
+ }
+ })
+ }
+}
+
+func TestEnvVarsFromConfigMap(t *testing.T) {
+ tests := []struct {
+ name string
+ envFrom v1.EnvFromSource
+ configMapList []v1.ConfigMap
+ expected map[string]string
+ }{
+ {
+ "ConfigMapExists",
+ v1.EnvFromSource{
+ ConfigMapRef: &v1.ConfigMapEnvSource{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "foo",
+ },
+ },
+ },
+ configMapList,
+ map[string]string{
+ "myvar": "foo",
+ },
+ },
+ {
+ "ConfigMapDoesNotExist",
+ v1.EnvFromSource{
+ ConfigMapRef: &v1.ConfigMapEnvSource{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "doesnotexist",
+ },
+ },
+ },
+ configMapList,
+ map[string]string{},
+ },
+ {
+ "EmptyConfigMapList",
+ v1.EnvFromSource{
+ ConfigMapRef: &v1.ConfigMapEnvSource{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "foo",
+ },
+ },
+ },
+ []v1.ConfigMap{},
+ map[string]string{},
+ },
+ }
+
+ for _, test := range tests {
+ test := test
+ t.Run(test.name, func(t *testing.T) {
+ result := envVarsFromConfigMap(test.envFrom, test.configMapList)
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
+
+func TestEnvVarValue(t *testing.T) {
+ tests := []struct {
+ name string
+ envVar v1.EnvVar
+ configMapList []v1.ConfigMap
+ expected string
+ }{
+ {
+ "ConfigMapExists",
+ v1.EnvVar{
+ Name: "FOO",
+ ValueFrom: &v1.EnvVarSource{
+ ConfigMapKeyRef: &v1.ConfigMapKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "foo",
+ },
+ Key: "myvar",
+ },
+ },
+ },
+ configMapList,
+ "foo",
+ },
+ {
+ "ContainerKeyDoesNotExistInConfigMap",
+ v1.EnvVar{
+ Name: "FOO",
+ ValueFrom: &v1.EnvVarSource{
+ ConfigMapKeyRef: &v1.ConfigMapKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "foo",
+ },
+ Key: "doesnotexist",
+ },
+ },
+ },
+ configMapList,
+ "",
+ },
+ {
+ "ConfigMapDoesNotExist",
+ v1.EnvVar{
+ Name: "FOO",
+ ValueFrom: &v1.EnvVarSource{
+ ConfigMapKeyRef: &v1.ConfigMapKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "doesnotexist",
+ },
+ Key: "myvar",
+ },
+ },
+ },
+ configMapList,
+ "",
+ },
+ {
+ "EmptyConfigMapList",
+ v1.EnvVar{
+ Name: "FOO",
+ ValueFrom: &v1.EnvVarSource{
+ ConfigMapKeyRef: &v1.ConfigMapKeySelector{
+ LocalObjectReference: v1.LocalObjectReference{
+ Name: "foo",
+ },
+ Key: "myvar",
+ },
+ },
+ },
+ []v1.ConfigMap{},
+ "",
+ },
+ }
+
+ for _, test := range tests {
+ test := test
+ t.Run(test.name, func(t *testing.T) {
+ result := envVarValue(test.envVar, test.configMapList)
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go
index 747da9fd4..258640a81 100644
--- a/pkg/domain/infra/abi/pods.go
+++ b/pkg/domain/infra/abi/pods.go
@@ -66,7 +66,7 @@ func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, opt
for _, p := range pods {
report := entities.PodKillReport{Id: p.ID()}
- conErrs, err := p.Kill(uint(sig))
+ conErrs, err := p.Kill(ctx, uint(sig))
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
report.Errs = []error{err}
reports = append(reports, &report)
@@ -92,7 +92,7 @@ func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, op
}
for _, p := range pods {
report := entities.PodPauseReport{Id: p.ID()}
- errs, err := p.Pause()
+ errs, err := p.Pause(ctx)
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
report.Errs = []error{err}
continue
@@ -117,7 +117,7 @@ func (ic *ContainerEngine) PodUnpause(ctx context.Context, namesOrIds []string,
}
for _, p := range pods {
report := entities.PodUnpauseReport{Id: p.ID()}
- errs, err := p.Unpause()
+ errs, err := p.Unpause(ctx)
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
report.Errs = []error{err}
continue
diff --git a/pkg/parallel/parallel_linux.go b/pkg/parallel/ctr/ctr.go
index 442db1502..e8c1292b8 100644
--- a/pkg/parallel/parallel_linux.go
+++ b/pkg/parallel/ctr/ctr.go
@@ -1,11 +1,10 @@
-package parallel
+package ctr
import (
"context"
- "sync"
"github.com/containers/podman/v2/libpod"
- "github.com/pkg/errors"
+ "github.com/containers/podman/v2/pkg/parallel"
"github.com/sirupsen/logrus"
)
@@ -14,44 +13,28 @@ import (
// If no error is returned, each container specified in ctrs will have an entry
// in the resulting map; containers with no error will be set to nil.
func ContainerOp(ctx context.Context, ctrs []*libpod.Container, applyFunc func(*libpod.Container) error) (map[*libpod.Container]error, error) {
- jobControlLock.RLock()
- defer jobControlLock.RUnlock()
-
// We could use a sync.Map but given Go's lack of generic I'd rather
// just use a lock on a normal map...
// The expectation is that most of the time is spent in applyFunc
// anyways.
var (
- errMap = make(map[*libpod.Container]error)
- errLock sync.Mutex
- allDone sync.WaitGroup
+ errMap = make(map[*libpod.Container]<-chan error)
)
for _, ctr := range ctrs {
- // Block until a thread is available
- if err := jobControl.Acquire(ctx, 1); err != nil {
- return nil, errors.Wrapf(err, "error acquiring job control semaphore")
- }
-
- allDone.Add(1)
-
c := ctr
- go func() {
- logrus.Debugf("Launching job on container %s", c.ID())
-
- err := applyFunc(c)
- errLock.Lock()
- errMap[c] = err
- errLock.Unlock()
-
- allDone.Done()
- jobControl.Release(1)
- }()
+ logrus.Debugf("Starting parallel job on container %s", c.ID())
+ errChan := parallel.Enqueue(ctx, func() error {
+ return applyFunc(c)
+ })
+ errMap[c] = errChan
}
- allDone.Wait()
+ finalErr := make(map[*libpod.Container]error)
+ for ctr, errChan := range errMap {
+ err := <-errChan
+ finalErr[ctr] = err
+ }
- return errMap, nil
+ return finalErr, nil
}
-
-// TODO: Add an Enqueue() function that returns a promise
diff --git a/pkg/parallel/parallel.go b/pkg/parallel/parallel.go
index c9e4da50d..4da7e0f89 100644
--- a/pkg/parallel/parallel.go
+++ b/pkg/parallel/parallel.go
@@ -1,6 +1,7 @@
package parallel
import (
+ "context"
"sync"
"github.com/pkg/errors"
@@ -42,3 +43,32 @@ func SetMaxThreads(threads uint) error {
func GetMaxThreads() uint {
return numThreads
}
+
+// Enqueue adds a single function to the parallel jobs queue. This function will
+// be run when an unused thread is available.
+// Returns a receive-only error channel that will return the error (if any) from
+// the provided function fn when fn has finished executing. The channel will be
+// closed after this.
+func Enqueue(ctx context.Context, fn func() error) <-chan error {
+ retChan := make(chan error)
+
+ go func() {
+ jobControlLock.RLock()
+ defer jobControlLock.RUnlock()
+
+ defer close(retChan)
+
+ if err := jobControl.Acquire(ctx, 1); err != nil {
+ retChan <- errors.Wrapf(err, "error acquiring job control semaphore")
+ return
+ }
+
+ err := fn()
+
+ jobControl.Release(1)
+
+ retChan <- err
+ }()
+
+ return retChan
+}
diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go
index 189434780..6d03afb7a 100644
--- a/pkg/varlinkapi/pods.go
+++ b/pkg/varlinkapi/pods.go
@@ -3,6 +3,7 @@
package varlinkapi
import (
+ "context"
"encoding/json"
"fmt"
"strconv"
@@ -207,7 +208,7 @@ func (i *VarlinkAPI) KillPod(call iopodman.VarlinkCall, name string, signal int6
if err != nil {
return call.ReplyPodNotFound(name, err.Error())
}
- ctrErrs, err := pod.Kill(killSignal)
+ ctrErrs, err := pod.Kill(context.TODO(), killSignal)
callErr := handlePodCall(call, pod, ctrErrs, err)
if callErr != nil {
return err
@@ -221,7 +222,7 @@ func (i *VarlinkAPI) PausePod(call iopodman.VarlinkCall, name string) error {
if err != nil {
return call.ReplyPodNotFound(name, err.Error())
}
- ctrErrs, err := pod.Pause()
+ ctrErrs, err := pod.Pause(context.TODO())
callErr := handlePodCall(call, pod, ctrErrs, err)
if callErr != nil {
return err
@@ -235,7 +236,7 @@ func (i *VarlinkAPI) UnpausePod(call iopodman.VarlinkCall, name string) error {
if err != nil {
return call.ReplyPodNotFound(name, err.Error())
}
- ctrErrs, err := pod.Unpause()
+ ctrErrs, err := pod.Unpause(context.TODO())
callErr := handlePodCall(call, pod, ctrErrs, err)
if callErr != nil {
return err