Move exported constants/functions from dockertools to dockershim
Previously we exported many constants and functions in dockertools to share with the dockershim package. This change moves such constants/functions to dockershim and unexport them. This change involves only mechnical changes and should not have any functional impact.
This commit is contained in:
		@@ -17,6 +17,8 @@ limitations under the License.
 | 
			
		||||
package dockershim
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	dockertypes "github.com/docker/engine-api/types"
 | 
			
		||||
	runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/dockertools"
 | 
			
		||||
@@ -80,7 +82,7 @@ func (ds *dockerService) PullImage(image *runtimeapi.ImageSpec, auth *runtimeapi
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return dockertools.GetImageRef(ds.client, image.Image)
 | 
			
		||||
	return getImageRef(ds.client, image.Image)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveImage removes the image.
 | 
			
		||||
@@ -101,3 +103,21 @@ func (ds *dockerService) RemoveImage(image *runtimeapi.ImageSpec) error {
 | 
			
		||||
	_, err = ds.client.RemoveImage(image.Image, dockertypes.ImageRemoveOptions{PruneChildren: true})
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getImageRef returns the image digest if exists, or else returns the image ID.
 | 
			
		||||
func getImageRef(client dockertools.DockerInterface, image string) (string, error) {
 | 
			
		||||
	img, err := client.InspectImageByRef(image)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if img == nil {
 | 
			
		||||
		return "", fmt.Errorf("unable to inspect image %s", image)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Returns the digest if it exist.
 | 
			
		||||
	if len(img.RepoDigests) > 0 {
 | 
			
		||||
		return img.RepoDigests[0], nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return img.ID, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,12 +20,14 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/blang/semver"
 | 
			
		||||
	dockertypes "github.com/docker/engine-api/types"
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/v1"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/componentconfig"
 | 
			
		||||
	internalapi "k8s.io/kubernetes/pkg/kubelet/api"
 | 
			
		||||
@@ -489,7 +491,32 @@ func (d *dockerLegacyService) GetContainerLogs(pod *v1.Pod, containerID kubecont
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return dockertools.GetContainerLogs(d.client, pod, containerID, logOptions, stdout, stderr, container.Config.Tty)
 | 
			
		||||
 | 
			
		||||
	var since int64
 | 
			
		||||
	if logOptions.SinceSeconds != nil {
 | 
			
		||||
		t := metav1.Now().Add(-time.Duration(*logOptions.SinceSeconds) * time.Second)
 | 
			
		||||
		since = t.Unix()
 | 
			
		||||
	}
 | 
			
		||||
	if logOptions.SinceTime != nil {
 | 
			
		||||
		since = logOptions.SinceTime.Unix()
 | 
			
		||||
	}
 | 
			
		||||
	opts := dockertypes.ContainerLogsOptions{
 | 
			
		||||
		ShowStdout: true,
 | 
			
		||||
		ShowStderr: true,
 | 
			
		||||
		Since:      strconv.FormatInt(since, 10),
 | 
			
		||||
		Timestamps: logOptions.Timestamps,
 | 
			
		||||
		Follow:     logOptions.Follow,
 | 
			
		||||
	}
 | 
			
		||||
	if logOptions.TailLines != nil {
 | 
			
		||||
		opts.Tail = strconv.FormatInt(*logOptions.TailLines, 10)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sopts := dockertools.StreamOptions{
 | 
			
		||||
		OutputStream: stdout,
 | 
			
		||||
		ErrorStream:  stderr,
 | 
			
		||||
		RawTerminal:  container.Config.Tty,
 | 
			
		||||
	}
 | 
			
		||||
	return d.client.Logs(containerID.ID, opts, sopts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// criSupportedLogDrivers are log drivers supported by native CRI integration.
 | 
			
		||||
 
 | 
			
		||||
@@ -21,11 +21,15 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"math"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	dockertypes "github.com/docker/engine-api/types"
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
 | 
			
		||||
	runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
 | 
			
		||||
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/dockertools"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/server/streaming"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/util/ioutils"
 | 
			
		||||
@@ -57,14 +61,14 @@ func (r *streamingRuntime) Attach(containerID string, in io.Reader, out, errw io
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return dockertools.AttachContainer(r.client, containerID, in, out, errw, tty, resize)
 | 
			
		||||
	return attachContainer(r.client, containerID, in, out, errw, tty, resize)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *streamingRuntime) PortForward(podSandboxID string, port int32, stream io.ReadWriteCloser) error {
 | 
			
		||||
	if port < 0 || port > math.MaxUint16 {
 | 
			
		||||
		return fmt.Errorf("invalid port %d", port)
 | 
			
		||||
	}
 | 
			
		||||
	return dockertools.PortForward(r.client, podSandboxID, port, stream)
 | 
			
		||||
	return portForward(r.client, podSandboxID, port, stream)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExecSync executes a command in the container, and returns the stdout output.
 | 
			
		||||
@@ -128,3 +132,83 @@ func checkContainerStatus(client dockertools.DockerInterface, containerID string
 | 
			
		||||
	}
 | 
			
		||||
	return container, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func attachContainer(client dockertools.DockerInterface, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
 | 
			
		||||
	// Have to start this before the call to client.AttachToContainer because client.AttachToContainer is a blocking
 | 
			
		||||
	// call :-( Otherwise, resize events don't get processed and the terminal never resizes.
 | 
			
		||||
	kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) {
 | 
			
		||||
		client.ResizeContainerTTY(containerID, int(size.Height), int(size.Width))
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// TODO(random-liu): Do we really use the *Logs* field here?
 | 
			
		||||
	opts := dockertypes.ContainerAttachOptions{
 | 
			
		||||
		Stream: true,
 | 
			
		||||
		Stdin:  stdin != nil,
 | 
			
		||||
		Stdout: stdout != nil,
 | 
			
		||||
		Stderr: stderr != nil,
 | 
			
		||||
	}
 | 
			
		||||
	sopts := dockertools.StreamOptions{
 | 
			
		||||
		InputStream:  stdin,
 | 
			
		||||
		OutputStream: stdout,
 | 
			
		||||
		ErrorStream:  stderr,
 | 
			
		||||
		RawTerminal:  tty,
 | 
			
		||||
	}
 | 
			
		||||
	return client.AttachToContainer(containerID, opts, sopts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func portForward(client dockertools.DockerInterface, podInfraContainerID string, port int32, stream io.ReadWriteCloser) error {
 | 
			
		||||
	container, err := client.InspectContainer(podInfraContainerID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !container.State.Running {
 | 
			
		||||
		return fmt.Errorf("container not running (%s)", container.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	containerPid := container.State.Pid
 | 
			
		||||
	socatPath, lookupErr := exec.LookPath("socat")
 | 
			
		||||
	if lookupErr != nil {
 | 
			
		||||
		return fmt.Errorf("unable to do port forwarding: socat not found.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	args := []string{"-t", fmt.Sprintf("%d", containerPid), "-n", socatPath, "-", fmt.Sprintf("TCP4:localhost:%d", port)}
 | 
			
		||||
 | 
			
		||||
	nsenterPath, lookupErr := exec.LookPath("nsenter")
 | 
			
		||||
	if lookupErr != nil {
 | 
			
		||||
		return fmt.Errorf("unable to do port forwarding: nsenter not found.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	commandString := fmt.Sprintf("%s %s", nsenterPath, strings.Join(args, " "))
 | 
			
		||||
	glog.V(4).Infof("executing port forwarding command: %s", commandString)
 | 
			
		||||
 | 
			
		||||
	command := exec.Command(nsenterPath, args...)
 | 
			
		||||
	command.Stdout = stream
 | 
			
		||||
 | 
			
		||||
	stderr := new(bytes.Buffer)
 | 
			
		||||
	command.Stderr = stderr
 | 
			
		||||
 | 
			
		||||
	// If we use Stdin, command.Run() won't return until the goroutine that's copying
 | 
			
		||||
	// from stream finishes. Unfortunately, if you have a client like telnet connected
 | 
			
		||||
	// via port forwarding, as long as the user's telnet client is connected to the user's
 | 
			
		||||
	// local listener that port forwarding sets up, the telnet session never exits. This
 | 
			
		||||
	// means that even if socat has finished running, command.Run() won't ever return
 | 
			
		||||
	// (because the client still has the connection and stream open).
 | 
			
		||||
	//
 | 
			
		||||
	// The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe
 | 
			
		||||
	// when the command (socat) exits.
 | 
			
		||||
	inPipe, err := command.StdinPipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("unable to do port forwarding: error creating stdin pipe: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	go func() {
 | 
			
		||||
		io.Copy(inPipe, stream)
 | 
			
		||||
		inPipe.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	if err := command.Run(); err != nil {
 | 
			
		||||
		return fmt.Errorf("%v: %s", err, stderr.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,12 @@ limitations under the License.
 | 
			
		||||
package dockershim
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/md5"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -28,14 +33,21 @@ import (
 | 
			
		||||
	dockernat "github.com/docker/go-connections/nat"
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/v1"
 | 
			
		||||
	v1helper "k8s.io/kubernetes/pkg/api/v1/helper"
 | 
			
		||||
	runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/dockertools"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/types"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/security/apparmor"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	annotationPrefix = "annotation."
 | 
			
		||||
 | 
			
		||||
	// Docker changed the API for specifying options in v1.11
 | 
			
		||||
	securityOptSeparatorChangeVersion = "1.23.0" // Corresponds to docker 1.11.x
 | 
			
		||||
	securityOptSeparatorOld           = ':'
 | 
			
		||||
	securityOptSeparatorNew           = '='
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@@ -43,7 +55,9 @@ var (
 | 
			
		||||
 | 
			
		||||
	// Docker changes the security option separator from ':' to '=' in the 1.23
 | 
			
		||||
	// API version.
 | 
			
		||||
	optsSeparatorChangeVersion = semver.MustParse(dockertools.SecurityOptSeparatorChangeVersion)
 | 
			
		||||
	optsSeparatorChangeVersion = semver.MustParse(securityOptSeparatorChangeVersion)
 | 
			
		||||
 | 
			
		||||
	defaultSeccompOpt = []dockerOpt{{"seccomp", "unconfined", ""}}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// generateEnvList converts KeyValue list to a list of strings, in the form of
 | 
			
		||||
@@ -181,17 +195,57 @@ func makePortsAndBindings(pm []*runtimeapi.PortMapping) (map[dockernat.Port]stru
 | 
			
		||||
	return exposedPorts, portBindings
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getSeccompDockerOpts(annotations map[string]string, ctrName, profileRoot string) ([]dockerOpt, error) {
 | 
			
		||||
	profile, profileOK := annotations[v1.SeccompContainerAnnotationKeyPrefix+ctrName]
 | 
			
		||||
	if !profileOK {
 | 
			
		||||
		// try the pod profile
 | 
			
		||||
		profile, profileOK = annotations[v1.SeccompPodAnnotationKey]
 | 
			
		||||
		if !profileOK {
 | 
			
		||||
			// return early the default
 | 
			
		||||
			return defaultSeccompOpt, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if profile == "unconfined" {
 | 
			
		||||
		// return early the default
 | 
			
		||||
		return defaultSeccompOpt, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if profile == "docker/default" {
 | 
			
		||||
		// return nil so docker will load the default seccomp profile
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !strings.HasPrefix(profile, "localhost/") {
 | 
			
		||||
		return nil, fmt.Errorf("unknown seccomp profile option: %s", profile)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	name := strings.TrimPrefix(profile, "localhost/") // by pod annotation validation, name is a valid subpath
 | 
			
		||||
	fname := filepath.Join(profileRoot, filepath.FromSlash(name))
 | 
			
		||||
	file, err := ioutil.ReadFile(fname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("cannot load seccomp profile %q: %v", name, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b := bytes.NewBuffer(nil)
 | 
			
		||||
	if err := json.Compact(b, file); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	// Rather than the full profile, just put the filename & md5sum in the event log.
 | 
			
		||||
	msg := fmt.Sprintf("%s(md5:%x)", name, md5.Sum(file))
 | 
			
		||||
 | 
			
		||||
	return []dockerOpt{{"seccomp", b.String(), msg}}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getSeccompSecurityOpts gets container seccomp options from container and sandbox
 | 
			
		||||
// config, currently from sandbox annotations.
 | 
			
		||||
// It is an experimental feature and may be promoted to official runtime api in the future.
 | 
			
		||||
func getSeccompSecurityOpts(containerName string, sandboxConfig *runtimeapi.PodSandboxConfig, seccompProfileRoot string, separator rune) ([]string, error) {
 | 
			
		||||
	seccompOpts, err := dockertools.GetSeccompOpts(sandboxConfig.GetAnnotations(), containerName, seccompProfileRoot)
 | 
			
		||||
	seccompOpts, err := getSeccompDockerOpts(sandboxConfig.GetAnnotations(), containerName, seccompProfileRoot)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmtOpts := dockertools.FmtDockerOpts(seccompOpts, separator)
 | 
			
		||||
	return fmtOpts, nil
 | 
			
		||||
	return fmtDockerOpts(seccompOpts, separator), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getApparmorSecurityOpts gets apparmor options from container config.
 | 
			
		||||
@@ -200,12 +254,12 @@ func getApparmorSecurityOpts(sc *runtimeapi.LinuxContainerSecurityContext, separ
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	appArmorOpts, err := dockertools.GetAppArmorOpts(sc.ApparmorProfile)
 | 
			
		||||
	appArmorOpts, err := getAppArmorOpts(sc.ApparmorProfile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmtOpts := dockertools.FmtDockerOpts(appArmorOpts, separator)
 | 
			
		||||
	fmtOpts := fmtDockerOpts(appArmorOpts, separator)
 | 
			
		||||
	return fmtOpts, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -258,10 +312,23 @@ func (f *dockerFilter) AddLabel(key, value string) {
 | 
			
		||||
	f.Add("label", fmt.Sprintf("%s=%s", key, value))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseUserFromImageUser splits the user out of an user:group string.
 | 
			
		||||
func parseUserFromImageUser(id string) string {
 | 
			
		||||
	if id == "" {
 | 
			
		||||
		return id
 | 
			
		||||
	}
 | 
			
		||||
	// split instances where the id may contain user:group
 | 
			
		||||
	if strings.Contains(id, ":") {
 | 
			
		||||
		return strings.Split(id, ":")[0]
 | 
			
		||||
	}
 | 
			
		||||
	// no group, just return the id
 | 
			
		||||
	return id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getUserFromImageUser gets uid or user name of the image user.
 | 
			
		||||
// If user is numeric, it will be treated as uid; or else, it is treated as user name.
 | 
			
		||||
func getUserFromImageUser(imageUser string) (*int64, string) {
 | 
			
		||||
	user := dockertools.GetUserFromImageUser(imageUser)
 | 
			
		||||
	user := parseUserFromImageUser(imageUser)
 | 
			
		||||
	// return both nil if user is not specified in the image.
 | 
			
		||||
	if user == "" {
 | 
			
		||||
		return nil, ""
 | 
			
		||||
@@ -321,9 +388,9 @@ func getSecurityOptSeparator(v *semver.Version) rune {
 | 
			
		||||
	case -1:
 | 
			
		||||
		// Current version is less than the API change version; use the old
 | 
			
		||||
		// separator.
 | 
			
		||||
		return dockertools.SecurityOptSeparatorOld
 | 
			
		||||
		return securityOptSeparatorOld
 | 
			
		||||
	default:
 | 
			
		||||
		return dockertools.SecurityOptSeparatorNew
 | 
			
		||||
		return securityOptSeparatorNew
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -342,3 +409,35 @@ func ensureSandboxImageExists(client dockertools.DockerInterface, image string)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAppArmorOpts(profile string) ([]dockerOpt, error) {
 | 
			
		||||
	if profile == "" || profile == apparmor.ProfileRuntimeDefault {
 | 
			
		||||
		// The docker applies the default profile by default.
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Assume validation has already happened.
 | 
			
		||||
	profileName := strings.TrimPrefix(profile, apparmor.ProfileNamePrefix)
 | 
			
		||||
	return []dockerOpt{{"apparmor", profileName, ""}}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fmtDockerOpts formats the docker security options using the given separator.
 | 
			
		||||
func fmtDockerOpts(opts []dockerOpt, sep rune) []string {
 | 
			
		||||
	fmtOpts := make([]string, len(opts))
 | 
			
		||||
	for i, opt := range opts {
 | 
			
		||||
		fmtOpts[i] = fmt.Sprintf("%s%c%s", opt.key, sep, opt.value)
 | 
			
		||||
	}
 | 
			
		||||
	return fmtOpts
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dockerOpt struct {
 | 
			
		||||
	// The key-value pair passed to docker.
 | 
			
		||||
	key, value string
 | 
			
		||||
	// The alternative value to use in log/event messages.
 | 
			
		||||
	msg string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Expose key/value from dockertools
 | 
			
		||||
func (d dockerOpt) GetKV() (string, string) {
 | 
			
		||||
	return d.key, d.value
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,38 +16,8 @@ limitations under the License.
 | 
			
		||||
 | 
			
		||||
package dockertools
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/md5"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	dockertypes "github.com/docker/engine-api/types"
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/v1"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
 | 
			
		||||
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/security/apparmor"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	DockerType                 = "docker"
 | 
			
		||||
	dockerDefaultLoggingDriver = "json-file"
 | 
			
		||||
 | 
			
		||||
	// Docker changed the API for specifying options in v1.11
 | 
			
		||||
	SecurityOptSeparatorChangeVersion = "1.23.0" // Corresponds to docker 1.11.x
 | 
			
		||||
	SecurityOptSeparatorOld           = ':'
 | 
			
		||||
	SecurityOptSeparatorNew           = '='
 | 
			
		||||
	DockerType = "docker"
 | 
			
		||||
 | 
			
		||||
	// https://docs.docker.com/engine/reference/api/docker_remote_api/
 | 
			
		||||
	// docker version should be at least 1.10.x
 | 
			
		||||
@@ -56,284 +26,4 @@ const (
 | 
			
		||||
	statusRunningPrefix = "Up"
 | 
			
		||||
	statusExitedPrefix  = "Exited"
 | 
			
		||||
	statusCreatedPrefix = "Created"
 | 
			
		||||
 | 
			
		||||
	ndotsDNSOption = "options ndots:5\n"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	defaultSeccompOpt = []dockerOpt{{"seccomp", "unconfined", ""}}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetImageRef returns the image digest if exists, or else returns the image ID.
 | 
			
		||||
// It is exported for reusing in dockershim.
 | 
			
		||||
func GetImageRef(client DockerInterface, image string) (string, error) {
 | 
			
		||||
	img, err := client.InspectImageByRef(image)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if img == nil {
 | 
			
		||||
		return "", fmt.Errorf("unable to inspect image %s", image)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Returns the digest if it exist.
 | 
			
		||||
	if len(img.RepoDigests) > 0 {
 | 
			
		||||
		return img.RepoDigests[0], nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return img.ID, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Temporarily export this function to share with dockershim.
 | 
			
		||||
// TODO: clean this up.
 | 
			
		||||
func GetContainerLogs(client DockerInterface, pod *v1.Pod, containerID kubecontainer.ContainerID, logOptions *v1.PodLogOptions, stdout, stderr io.Writer, rawTerm bool) error {
 | 
			
		||||
	var since int64
 | 
			
		||||
	if logOptions.SinceSeconds != nil {
 | 
			
		||||
		t := metav1.Now().Add(-time.Duration(*logOptions.SinceSeconds) * time.Second)
 | 
			
		||||
		since = t.Unix()
 | 
			
		||||
	}
 | 
			
		||||
	if logOptions.SinceTime != nil {
 | 
			
		||||
		since = logOptions.SinceTime.Unix()
 | 
			
		||||
	}
 | 
			
		||||
	opts := dockertypes.ContainerLogsOptions{
 | 
			
		||||
		ShowStdout: true,
 | 
			
		||||
		ShowStderr: true,
 | 
			
		||||
		Since:      strconv.FormatInt(since, 10),
 | 
			
		||||
		Timestamps: logOptions.Timestamps,
 | 
			
		||||
		Follow:     logOptions.Follow,
 | 
			
		||||
	}
 | 
			
		||||
	if logOptions.TailLines != nil {
 | 
			
		||||
		opts.Tail = strconv.FormatInt(*logOptions.TailLines, 10)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sopts := StreamOptions{
 | 
			
		||||
		OutputStream: stdout,
 | 
			
		||||
		ErrorStream:  stderr,
 | 
			
		||||
		RawTerminal:  rawTerm,
 | 
			
		||||
	}
 | 
			
		||||
	return client.Logs(containerID.ID, opts, sopts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Temporarily export this function to share with dockershim.
 | 
			
		||||
// TODO: clean this up.
 | 
			
		||||
func AttachContainer(client DockerInterface, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
 | 
			
		||||
	// Have to start this before the call to client.AttachToContainer because client.AttachToContainer is a blocking
 | 
			
		||||
	// call :-( Otherwise, resize events don't get processed and the terminal never resizes.
 | 
			
		||||
	kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) {
 | 
			
		||||
		client.ResizeContainerTTY(containerID, int(size.Height), int(size.Width))
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// TODO(random-liu): Do we really use the *Logs* field here?
 | 
			
		||||
	opts := dockertypes.ContainerAttachOptions{
 | 
			
		||||
		Stream: true,
 | 
			
		||||
		Stdin:  stdin != nil,
 | 
			
		||||
		Stdout: stdout != nil,
 | 
			
		||||
		Stderr: stderr != nil,
 | 
			
		||||
	}
 | 
			
		||||
	sopts := StreamOptions{
 | 
			
		||||
		InputStream:  stdin,
 | 
			
		||||
		OutputStream: stdout,
 | 
			
		||||
		ErrorStream:  stderr,
 | 
			
		||||
		RawTerminal:  tty,
 | 
			
		||||
	}
 | 
			
		||||
	return client.AttachToContainer(containerID, opts, sopts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Temporarily export this function to share with dockershim.
 | 
			
		||||
func PortForward(client DockerInterface, podInfraContainerID string, port int32, stream io.ReadWriteCloser) error {
 | 
			
		||||
	container, err := client.InspectContainer(podInfraContainerID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !container.State.Running {
 | 
			
		||||
		return fmt.Errorf("container not running (%s)", container.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	containerPid := container.State.Pid
 | 
			
		||||
	socatPath, lookupErr := exec.LookPath("socat")
 | 
			
		||||
	if lookupErr != nil {
 | 
			
		||||
		return fmt.Errorf("unable to do port forwarding: socat not found.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	args := []string{"-t", fmt.Sprintf("%d", containerPid), "-n", socatPath, "-", fmt.Sprintf("TCP4:localhost:%d", port)}
 | 
			
		||||
 | 
			
		||||
	nsenterPath, lookupErr := exec.LookPath("nsenter")
 | 
			
		||||
	if lookupErr != nil {
 | 
			
		||||
		return fmt.Errorf("unable to do port forwarding: nsenter not found.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	commandString := fmt.Sprintf("%s %s", nsenterPath, strings.Join(args, " "))
 | 
			
		||||
	glog.V(4).Infof("executing port forwarding command: %s", commandString)
 | 
			
		||||
 | 
			
		||||
	command := exec.Command(nsenterPath, args...)
 | 
			
		||||
	command.Stdout = stream
 | 
			
		||||
 | 
			
		||||
	stderr := new(bytes.Buffer)
 | 
			
		||||
	command.Stderr = stderr
 | 
			
		||||
 | 
			
		||||
	// If we use Stdin, command.Run() won't return until the goroutine that's copying
 | 
			
		||||
	// from stream finishes. Unfortunately, if you have a client like telnet connected
 | 
			
		||||
	// via port forwarding, as long as the user's telnet client is connected to the user's
 | 
			
		||||
	// local listener that port forwarding sets up, the telnet session never exits. This
 | 
			
		||||
	// means that even if socat has finished running, command.Run() won't ever return
 | 
			
		||||
	// (because the client still has the connection and stream open).
 | 
			
		||||
	//
 | 
			
		||||
	// The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe
 | 
			
		||||
	// when the command (socat) exits.
 | 
			
		||||
	inPipe, err := command.StdinPipe()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("unable to do port forwarding: error creating stdin pipe: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	go func() {
 | 
			
		||||
		io.Copy(inPipe, stream)
 | 
			
		||||
		inPipe.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	if err := command.Run(); err != nil {
 | 
			
		||||
		return fmt.Errorf("%v: %s", err, stderr.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Temporarily export this function to share with dockershim.
 | 
			
		||||
// TODO: clean this up.
 | 
			
		||||
func GetAppArmorOpts(profile string) ([]dockerOpt, error) {
 | 
			
		||||
	if profile == "" || profile == apparmor.ProfileRuntimeDefault {
 | 
			
		||||
		// The docker applies the default profile by default.
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Assume validation has already happened.
 | 
			
		||||
	profileName := strings.TrimPrefix(profile, apparmor.ProfileNamePrefix)
 | 
			
		||||
	return []dockerOpt{{"apparmor", profileName, ""}}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Temporarily export this function to share with dockershim.
 | 
			
		||||
// TODO: clean this up.
 | 
			
		||||
func GetSeccompOpts(annotations map[string]string, ctrName, profileRoot string) ([]dockerOpt, error) {
 | 
			
		||||
	profile, profileOK := annotations[v1.SeccompContainerAnnotationKeyPrefix+ctrName]
 | 
			
		||||
	if !profileOK {
 | 
			
		||||
		// try the pod profile
 | 
			
		||||
		profile, profileOK = annotations[v1.SeccompPodAnnotationKey]
 | 
			
		||||
		if !profileOK {
 | 
			
		||||
			// return early the default
 | 
			
		||||
			return defaultSeccompOpt, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if profile == "unconfined" {
 | 
			
		||||
		// return early the default
 | 
			
		||||
		return defaultSeccompOpt, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if profile == "docker/default" {
 | 
			
		||||
		// return nil so docker will load the default seccomp profile
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !strings.HasPrefix(profile, "localhost/") {
 | 
			
		||||
		return nil, fmt.Errorf("unknown seccomp profile option: %s", profile)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	name := strings.TrimPrefix(profile, "localhost/") // by pod annotation validation, name is a valid subpath
 | 
			
		||||
	fname := filepath.Join(profileRoot, filepath.FromSlash(name))
 | 
			
		||||
	file, err := ioutil.ReadFile(fname)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("cannot load seccomp profile %q: %v", name, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b := bytes.NewBuffer(nil)
 | 
			
		||||
	if err := json.Compact(b, file); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	// Rather than the full profile, just put the filename & md5sum in the event log.
 | 
			
		||||
	msg := fmt.Sprintf("%s(md5:%x)", name, md5.Sum(file))
 | 
			
		||||
 | 
			
		||||
	return []dockerOpt{{"seccomp", b.String(), msg}}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FmtDockerOpts formats the docker security options using the given separator.
 | 
			
		||||
func FmtDockerOpts(opts []dockerOpt, sep rune) []string {
 | 
			
		||||
	fmtOpts := make([]string, len(opts))
 | 
			
		||||
	for i, opt := range opts {
 | 
			
		||||
		fmtOpts[i] = fmt.Sprintf("%s%c%s", opt.key, sep, opt.value)
 | 
			
		||||
	}
 | 
			
		||||
	return fmtOpts
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dockerOpt struct {
 | 
			
		||||
	// The key-value pair passed to docker.
 | 
			
		||||
	key, value string
 | 
			
		||||
	// The alternative value to use in log/event messages.
 | 
			
		||||
	msg string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Expose key/value from dockertools
 | 
			
		||||
func (d dockerOpt) GetKV() (string, string) {
 | 
			
		||||
	return d.key, d.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUserFromImageUser splits the user out of an user:group string.
 | 
			
		||||
func GetUserFromImageUser(id string) string {
 | 
			
		||||
	if id == "" {
 | 
			
		||||
		return id
 | 
			
		||||
	}
 | 
			
		||||
	// split instances where the id may contain user:group
 | 
			
		||||
	if strings.Contains(id, ":") {
 | 
			
		||||
		return strings.Split(id, ":")[0]
 | 
			
		||||
	}
 | 
			
		||||
	// no group, just return the id
 | 
			
		||||
	return id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RewriteResolvFile rewrites resolv.conf file generated by docker.
 | 
			
		||||
// Exported for reusing in dockershim.
 | 
			
		||||
func RewriteResolvFile(resolvFilePath string, dns []string, dnsSearch []string, useClusterFirstPolicy bool) error {
 | 
			
		||||
	if len(resolvFilePath) == 0 {
 | 
			
		||||
		glog.Errorf("ResolvConfPath is empty.")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := os.Stat(resolvFilePath); os.IsNotExist(err) {
 | 
			
		||||
		return fmt.Errorf("ResolvConfPath %q does not exist", resolvFilePath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var resolvFileContent []string
 | 
			
		||||
 | 
			
		||||
	for _, srv := range dns {
 | 
			
		||||
		resolvFileContent = append(resolvFileContent, "nameserver "+srv)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(dnsSearch) > 0 {
 | 
			
		||||
		resolvFileContent = append(resolvFileContent, "search "+strings.Join(dnsSearch, " "))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(resolvFileContent) > 0 {
 | 
			
		||||
		if useClusterFirstPolicy {
 | 
			
		||||
			resolvFileContent = append(resolvFileContent, ndotsDNSOption)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		resolvFileContentStr := strings.Join(resolvFileContent, "\n")
 | 
			
		||||
		resolvFileContentStr += "\n"
 | 
			
		||||
 | 
			
		||||
		glog.V(4).Infof("Will attempt to re-write config file %s with: \n%s", resolvFilePath, resolvFileContent)
 | 
			
		||||
		if err := rewriteFile(resolvFilePath, resolvFileContentStr); err != nil {
 | 
			
		||||
			glog.Errorf("resolv.conf could not be updated: %v", err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func rewriteFile(filePath, stringToWrite string) error {
 | 
			
		||||
	f, err := os.OpenFile(filePath, os.O_TRUNC|os.O_WRONLY, 0644)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	_, err = f.WriteString(stringToWrite)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user