summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/boltdb_state.go167
-rw-r--r--libpod/container.go15
-rw-r--r--libpod/container_internal.go37
-rw-r--r--libpod/container_internal_linux.go48
-rw-r--r--libpod/container_internal_test.go48
-rw-r--r--libpod/healthcheck.go92
-rw-r--r--libpod/image/image.go29
-rw-r--r--libpod/oci.go6
-rw-r--r--libpod/options.go12
-rw-r--r--libpod/runtime_pod_linux.go5
10 files changed, 381 insertions, 78 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go
index c226a0617..92a7b1538 100644
--- a/libpod/boltdb_state.go
+++ b/libpod/boltdb_state.go
@@ -382,6 +382,11 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
return err
}
+ namesBucket, err := getNamesBucket(tx)
+ if err != nil {
+ return err
+ }
+
nsBucket, err := getNSBucket(tx)
if err != nil {
return err
@@ -395,41 +400,59 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
// It might not be in our namespace, but
// getContainerFromDB() will handle that case.
id = []byte(idOrName)
- } else {
- // They did not give us a full container ID.
- // Search for partial ID or full name matches
- // 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
- }
+ return s.getContainerFromDB(id, ctr, ctrBucket)
+ }
+
+ // Next, check if the full name was given
+ isPod := false
+ fullID := namesBucket.Get([]byte(idOrName))
+ if fullID != nil {
+ // The name exists and maps to an ID.
+ // However, we are not yet certain the ID is a
+ // container.
+ ctrExists = ctrBucket.Bucket(fullID)
+ if ctrExists != nil {
+ // A container bucket matching the full ID was
+ // found.
+ return s.getContainerFromDB(fullID, ctr, ctrBucket)
+ }
+ // Don't error if we have a name match but it's not a
+ // container - there's a chance we have a container with
+ // an ID starting with those characters.
+ // However, so we can return a good error, note whether
+ // this is a pod.
+ isPod = true
+ }
+
+ // We were not given a full container ID or name.
+ // Search for partial ID matches.
+ 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)
- }
- id = checkID
- exists = true
- } else if strings.HasPrefix(string(checkID), idOrName) {
- if exists {
- return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName)
- }
- id = checkID
- exists = true
+ }
+ if strings.HasPrefix(string(checkID), idOrName) {
+ if exists {
+ return errors.Wrapf(ErrCtrExists, "more than one result for container ID %s", idOrName)
}
+ id = checkID
+ exists = true
+ }
- return nil
- })
- if err != nil {
- return err
- } else if !exists {
- return errors.Wrapf(ErrNoSuchCtr, "no container with name or ID %s found", idOrName)
+ return nil
+ })
+ if err != nil {
+ return err
+ } else if !exists {
+ if isPod {
+ return errors.Wrapf(ErrNoSuchCtr, "%s is a pod, not a container", idOrName)
}
+ return errors.Wrapf(ErrNoSuchCtr, "no container with name or ID %s found", idOrName)
}
return s.getContainerFromDB(id, ctr, ctrBucket)
@@ -941,6 +964,11 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) {
return err
}
+ namesBkt, err := getNamesBucket(tx)
+ if err != nil {
+ return err
+ }
+
nsBkt, err := getNSBucket(tx)
if err != nil {
return err
@@ -954,41 +982,56 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) {
// It might not be in our namespace, but getPodFromDB()
// will handle that case.
id = []byte(idOrName)
- } else {
- // They did not give us a full pod ID.
- // Search for partial ID or full name matches
- // 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
- }
+ return s.getPodFromDB(id, pod, podBkt)
+ }
+
+ // Next, check if the full name was given
+ isCtr := false
+ fullID := namesBkt.Get([]byte(idOrName))
+ if fullID != nil {
+ // The name exists and maps to an ID.
+ // However, we aren't yet sure if the ID is a pod.
+ podExists = podBkt.Bucket(fullID)
+ if podExists != nil {
+ // A pod bucket matching the full ID was found.
+ return s.getPodFromDB(fullID, pod, podBkt)
+ }
+ // Don't error if we have a name match but it's not a
+ // pod - there's a chance we have a pod with an ID
+ // starting with those characters.
+ // However, so we can return a good error, note whether
+ // this is a container.
+ isCtr = true
+ }
+ // They did not give us a full pod name or ID.
+ // Search for partial ID matches.
+ 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)
- }
- id = checkID
- exists = true
- } else if strings.HasPrefix(string(checkID), idOrName) {
- if exists {
- return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName)
- }
- id = checkID
- exists = true
+ }
+ if strings.HasPrefix(string(checkID), idOrName) {
+ if exists {
+ return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName)
}
+ id = checkID
+ exists = true
+ }
- return nil
- })
- if err != nil {
- return err
- } else if !exists {
- return errors.Wrapf(ErrNoSuchPod, "no pod with name or ID %s found", idOrName)
+ return nil
+ })
+ if err != nil {
+ return err
+ } else if !exists {
+ if isCtr {
+ return errors.Wrapf(ErrNoSuchPod, "%s is a container, not a pod", idOrName)
}
+ return errors.Wrapf(ErrNoSuchPod, "no pod with name or ID %s found", idOrName)
}
// We might have found a container ID, but it's OK
diff --git a/libpod/container.go b/libpod/container.go
index 75f4a4a4f..2381f53ad 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -10,6 +10,7 @@ import (
"github.com/containernetworking/cni/pkg/types"
cnitypes "github.com/containernetworking/cni/pkg/types/current"
+ "github.com/containers/image/manifest"
"github.com/containers/libpod/libpod/lock"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/storage"
@@ -365,6 +366,9 @@ type ContainerConfig struct {
// Systemd tells libpod to setup the container in systemd mode
Systemd bool `json:"systemd"`
+
+ // HealtchCheckConfig has the health check command and related timings
+ HealthCheckConfig *manifest.Schema2HealthConfig
}
// ContainerStatus returns a string representation for users
@@ -1085,3 +1089,14 @@ func (c *Container) ContainerState() (*ContainerState, error) {
deepcopier.Copy(c.state).To(returnConfig)
return c.state, nil
}
+
+// HasHealthCheck returns bool as to whether there is a health check
+// defined for the container
+func (c *Container) HasHealthCheck() bool {
+ return c.config.HealthCheckConfig != nil
+}
+
+// HealthCheckConfig returns the command and timing attributes of the health check
+func (c *Container) HealthCheckConfig() *manifest.Schema2HealthConfig {
+ return c.config.HealthCheckConfig
+}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index e3753d825..d86062e38 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -34,8 +34,8 @@ const (
)
var (
- // localeToLanguage maps from locale values to language tags.
- localeToLanguage = map[string]string{
+ // localeToLanguageMap maps from locale values to language tags.
+ localeToLanguageMap = map[string]string{
"": "und-u-va-posix",
"c": "und-u-va-posix",
"posix": "und-u-va-posix",
@@ -1231,6 +1231,23 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error)
return filepath.Join(c.state.DestinationRunDir, destFile), nil
}
+// appendStringToRundir appends the provided string to the runtimedir file
+func (c *Container) appendStringToRundir(destFile, output string) (string, error) {
+ destFileName := filepath.Join(c.state.RunDir, destFile)
+
+ f, err := os.OpenFile(destFileName, os.O_APPEND|os.O_WRONLY, 0600)
+ if err != nil {
+ return "", errors.Wrapf(err, "unable to open %s", destFileName)
+ }
+ defer f.Close()
+
+ if _, err := f.WriteString(output); err != nil {
+ return "", errors.Wrapf(err, "unable to write %s", destFileName)
+ }
+
+ return filepath.Join(c.state.DestinationRunDir, destFile), nil
+}
+
// Save OCI spec to disk, replacing any existing specs for the container
func (c *Container) saveSpec(spec *spec.Spec) error {
// If the OCI spec already exists, we need to replace it
@@ -1264,6 +1281,16 @@ func (c *Container) saveSpec(spec *spec.Spec) error {
return nil
}
+// localeToLanguage translates POSIX locale strings to BCP 47 language tags.
+func localeToLanguage(locale string) string {
+ locale = strings.Replace(strings.SplitN(locale, ".", 2)[0], "_", "-", 1)
+ langString, ok := localeToLanguageMap[strings.ToLower(locale)]
+ if !ok {
+ langString = locale
+ }
+ return langString
+}
+
// Warning: precreate hooks may alter 'config' in place.
func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (extensionStageHooks map[string][]spec.Hook, err error) {
var locale string
@@ -1279,11 +1306,7 @@ func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (exten
}
}
- langString, ok := localeToLanguage[strings.ToLower(locale)]
- if !ok {
- langString = locale
- }
-
+ langString := localeToLanguage(locale)
lang, err := language.Parse(langString)
if err != nil {
logrus.Warnf("failed to parse language %q: %s", langString, err)
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 0e9a5124e..5f9e5a20c 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -698,13 +698,29 @@ func (c *Container) makeBindMounts() error {
// If it doesn't, don't copy them
resolvPath, exists := bindMounts["/etc/resolv.conf"]
if exists {
-
c.state.BindMounts["/etc/resolv.conf"] = resolvPath
}
+
+ // check if dependency container has an /etc/hosts file
hostsPath, exists := bindMounts["/etc/hosts"]
- if exists {
- c.state.BindMounts["/etc/hosts"] = hostsPath
+ if !exists {
+ return errors.Errorf("error finding hosts file of dependency container %s for container %s", depCtr.ID(), c.ID())
+ }
+
+ depCtr.lock.Lock()
+ // generate a hosts file for the dependency container,
+ // based on either its old hosts file, or the default,
+ // and add the relevant information from the new container (hosts and IP)
+ hostsPath, err = depCtr.appendHosts(hostsPath, c)
+
+ if err != nil {
+ depCtr.lock.Unlock()
+ return errors.Wrapf(err, "error creating hosts file for container %s which depends on container %s", c.ID(), depCtr.ID())
}
+ depCtr.lock.Unlock()
+
+ // finally, save it in the new container
+ c.state.BindMounts["/etc/hosts"] = hostsPath
} else {
newResolv, err := c.generateResolvConf()
if err != nil {
@@ -712,7 +728,7 @@ func (c *Container) makeBindMounts() error {
}
c.state.BindMounts["/etc/resolv.conf"] = newResolv
- newHosts, err := c.generateHosts()
+ newHosts, err := c.generateHosts("/etc/hosts")
if err != nil {
return errors.Wrapf(err, "error creating hosts file for container %s", c.ID())
}
@@ -854,12 +870,28 @@ func (c *Container) generateResolvConf() (string, error) {
}
// generateHosts creates a containers hosts file
-func (c *Container) generateHosts() (string, error) {
- orig, err := ioutil.ReadFile("/etc/hosts")
+func (c *Container) generateHosts(path string) (string, error) {
+ orig, err := ioutil.ReadFile(path)
if err != nil {
- return "", errors.Wrapf(err, "unable to read /etc/hosts")
+ return "", errors.Wrapf(err, "unable to read %s", path)
}
hosts := string(orig)
+ hosts += c.getHosts()
+ return c.writeStringToRundir("hosts", hosts)
+}
+
+// appendHosts appends a container's config and state pertaining to hosts to a container's
+// local hosts file. netCtr is the container from which the netNS information is
+// taken.
+// path is the basis of the hosts file, into which netCtr's netNS information will be appended.
+func (c *Container) appendHosts(path string, netCtr *Container) (string, error) {
+ return c.appendStringToRundir("hosts", netCtr.getHosts())
+}
+
+// getHosts finds the pertinent information for a container's host file in its config and state
+// and returns a string in a format that can be written to the host file
+func (c *Container) getHosts() string {
+ var hosts string
if len(c.config.HostAdd) > 0 {
for _, host := range c.config.HostAdd {
// the host format has already been verified at this point
@@ -871,7 +903,7 @@ func (c *Container) generateHosts() (string, error) {
ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0]
hosts += fmt.Sprintf("%s\t%s\n", ipAddress, c.Hostname())
}
- return c.writeStringToRundir("hosts", hosts)
+ return hosts
}
// generatePasswd generates a container specific passwd file,
diff --git a/libpod/container_internal_test.go b/libpod/container_internal_test.go
index f1e2b70a7..1654af929 100644
--- a/libpod/container_internal_test.go
+++ b/libpod/container_internal_test.go
@@ -17,6 +17,54 @@ import (
// hookPath is the path to an example hook executable.
var hookPath string
+func TestLocaleToLanguage(t *testing.T) {
+ for _, testCase := range []struct {
+ locale string
+ language string
+ }{
+ {
+ locale: "",
+ language: "und-u-va-posix",
+ },
+ {
+ locale: "C",
+ language: "und-u-va-posix",
+ },
+ {
+ locale: "POSIX",
+ language: "und-u-va-posix",
+ },
+ {
+ locale: "c",
+ language: "und-u-va-posix",
+ },
+ {
+ locale: "en",
+ language: "en",
+ },
+ {
+ locale: "en_US",
+ language: "en-US",
+ },
+ {
+ locale: "en.UTF-8",
+ language: "en",
+ },
+ {
+ locale: "en_US.UTF-8",
+ language: "en-US",
+ },
+ {
+ locale: "does-not-exist",
+ language: "does-not-exist",
+ },
+ } {
+ t.Run(testCase.locale, func(t *testing.T) {
+ assert.Equal(t, testCase.language, localeToLanguage(testCase.locale))
+ })
+ }
+}
+
func TestPostDeleteHooks(t *testing.T) {
ctx := context.Background()
dir, err := ioutil.TempDir("", "libpod_test_")
diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go
new file mode 100644
index 000000000..81addb9a8
--- /dev/null
+++ b/libpod/healthcheck.go
@@ -0,0 +1,92 @@
+package libpod
+
+import (
+ "os"
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// HealthCheckStatus represents the current state of a container
+type HealthCheckStatus int
+
+const (
+ // HealthCheckSuccess means the health worked
+ HealthCheckSuccess HealthCheckStatus = iota
+ // HealthCheckFailure means the health ran and failed
+ HealthCheckFailure HealthCheckStatus = iota
+ // HealthCheckContainerStopped means the health check cannot
+ // be run because the container is stopped
+ HealthCheckContainerStopped HealthCheckStatus = iota
+ // HealthCheckContainerNotFound means the container could
+ // not be found in local store
+ HealthCheckContainerNotFound HealthCheckStatus = iota
+ // HealthCheckNotDefined means the container has no health
+ // check defined in it
+ HealthCheckNotDefined HealthCheckStatus = iota
+ // HealthCheckInternalError means somes something failed obtaining or running
+ // a given health check
+ HealthCheckInternalError HealthCheckStatus = iota
+ // HealthCheckDefined means the healthcheck was found on the container
+ HealthCheckDefined HealthCheckStatus = iota
+)
+
+// HealthCheck verifies the state and validity of the healthcheck configuration
+// on the container and then executes the healthcheck
+func (r *Runtime) HealthCheck(name string) (HealthCheckStatus, error) {
+ container, err := r.LookupContainer(name)
+ if err != nil {
+ return HealthCheckContainerNotFound, errors.Wrapf(err, "unable to lookup %s to perform a health check", name)
+ }
+ hcStatus, err := checkHealthCheckCanBeRun(container)
+ if err == nil {
+ return container.RunHealthCheck()
+ }
+ return hcStatus, err
+}
+
+// RunHealthCheck runs the health check as defined by the container
+func (c *Container) RunHealthCheck() (HealthCheckStatus, error) {
+ var newCommand []string
+ hcStatus, err := checkHealthCheckCanBeRun(c)
+ if err != nil {
+ return hcStatus, err
+ }
+ hcCommand := c.HealthCheckConfig().Test
+ if len(hcCommand) > 0 && hcCommand[0] == "CMD-SHELL" {
+ newCommand = []string{"sh", "-c"}
+ newCommand = append(newCommand, hcCommand[1:]...)
+ } else {
+ newCommand = hcCommand
+ }
+ // TODO when history/logging is implemented for healthcheck, we need to change the output streams
+ // so we can capture i/o
+ streams := new(AttachStreams)
+ streams.OutputStream = os.Stdout
+ streams.ErrorStream = os.Stderr
+ streams.InputStream = os.Stdin
+ streams.AttachOutput = true
+ streams.AttachError = true
+ streams.AttachInput = true
+
+ logrus.Debugf("executing health check command %s for %s", strings.Join(newCommand, " "), c.ID())
+ if err := c.Exec(false, false, []string{}, newCommand, "", "", streams, 0); err != nil {
+ return HealthCheckFailure, err
+ }
+ return HealthCheckSuccess, nil
+}
+
+func checkHealthCheckCanBeRun(c *Container) (HealthCheckStatus, error) {
+ cstate, err := c.State()
+ if err != nil {
+ return HealthCheckInternalError, err
+ }
+ if cstate != ContainerStateRunning {
+ return HealthCheckContainerStopped, errors.Errorf("container %s is not running", c.ID())
+ }
+ if !c.HasHealthCheck() {
+ return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID())
+ }
+ return HealthCheckDefined, nil
+}
diff --git a/libpod/image/image.go b/libpod/image/image.go
index b20419d7b..8c98de3d3 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -1151,3 +1151,32 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag
return nil
}
+
+// GetConfigBlob returns a schema2image. If the image is not a schema2, then
+// it will return an error
+func (i *Image) GetConfigBlob(ctx context.Context) (*manifest.Schema2Image, error) {
+ imageRef, err := i.toImageRef(ctx)
+ if err != nil {
+ return nil, err
+ }
+ b, err := imageRef.ConfigBlob(ctx)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to get config blob for %s", i.ID())
+ }
+ blob := manifest.Schema2Image{}
+ if err := json.Unmarshal(b, &blob); err != nil {
+ return nil, errors.Wrapf(err, "unable to parse image blob for %s", i.ID())
+ }
+ return &blob, nil
+
+}
+
+// GetHealthCheck returns a HealthConfig for an image. This function only works with
+// schema2 images.
+func (i *Image) GetHealthCheck(ctx context.Context) (*manifest.Schema2HealthConfig, error) {
+ configBlob, err := i.GetConfigBlob(ctx)
+ if err != nil {
+ return nil, err
+ }
+ return configBlob.ContainerConfig.Healthcheck, nil
+}
diff --git a/libpod/oci.go b/libpod/oci.go
index 2cbf25699..c3b5f9af2 100644
--- a/libpod/oci.go
+++ b/libpod/oci.go
@@ -805,6 +805,12 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty
execCmd.Env = append(execCmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir))
+ if preserveFDs > 0 {
+ for fd := 3; fd < 3+preserveFDs; fd++ {
+ execCmd.ExtraFiles = append(execCmd.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)))
+ }
+ }
+
if err := execCmd.Start(); err != nil {
return nil, errors.Wrapf(err, "cannot start container %s", c.ID())
}
diff --git a/libpod/options.go b/libpod/options.go
index 1e8592a25..5ad2824d9 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -7,6 +7,7 @@ import (
"regexp"
"syscall"
+ "github.com/containers/image/manifest"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
@@ -1469,3 +1470,14 @@ func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption {
return nil
}
}
+
+// WithHealthCheck adds the healthcheck to the container config
+func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return ErrCtrFinalized
+ }
+ ctr.config.HealthCheckConfig = healthCheck
+ return nil
+ }
+}
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index c378d18e4..9063390bd 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -95,9 +95,12 @@ func (r *Runtime) NewPod(ctx context.Context, options ...PodCreateOption) (*Pod,
if pod.config.UsePodCgroup {
logrus.Debugf("Got pod cgroup as %s", pod.state.CgroupPath)
}
- if pod.HasInfraContainer() != pod.SharesNamespaces() {
+ if !pod.HasInfraContainer() && pod.SharesNamespaces() {
return nil, errors.Errorf("Pods must have an infra container to share namespaces")
}
+ if pod.HasInfraContainer() && !pod.SharesNamespaces() {
+ logrus.Warnf("Pod has an infra container, but shares no namespaces")
+ }
if err := r.state.AddPod(pod); err != nil {
return nil, errors.Wrapf(err, "error adding pod to state")