dockertools

package
v1.6.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2017 License: Apache-2.0 Imports: 75 Imported by: 0

Documentation

Index

Constants

View Source
const (
	PodInfraContainerName = leaky.PodInfraContainerName
	DockerPrefix          = "docker://"
	DockerPullablePrefix  = "docker-pullable://"
	LogSuffix             = "log"
)
View Source
const (
	DockerType = "docker"

	DockerNetnsFmt = "/proc/%v/ns/net"

	// Docker changed the API for specifying options in v1.11
	SecurityOptSeparatorChangeVersion = "1.23.0" // Corresponds to docker 1.11.x
	SecurityOptSeparatorOld           = ':'
	SecurityOptSeparatorNew           = '='
)

Variables

View Source
var (
	// ErrContainerCannotRun is returned when a container is created, but cannot run properly
	ErrContainerCannotRun = errors.New("ContainerCannotRun")
)

Functions

func AttachContainer

func AttachContainer(client DockerInterface, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error

Temporarily export this function to share with dockershim. TODO: clean this up.

func BuildDockerName

func BuildDockerName(dockerName KubeletContainerName, container *v1.Container) (string, string, string)

Creates a name which can be reversed to identify both full pod name and container name. This function returns stable name, unique name and a unique id. Although rand.Uint32() is not really unique, but it's enough for us because error will only occur when instances of the same container in the same pod have the same UID. The chance is really slim.

func DefaultMemorySwap added in v1.5.3

func DefaultMemorySwap() int64

func FmtDockerOpts added in v1.6.0

func FmtDockerOpts(opts []dockerOpt, sep rune) []string

FmtDockerOpts formats the docker security options using the given separator.

func GetAppArmorOpts added in v1.5.0

func GetAppArmorOpts(annotations map[string]string, ctrName string) ([]dockerOpt, error)

Temporarily export this function to share with dockershim. TODO: clean this up.

func GetContainerLogs

func GetContainerLogs(client DockerInterface, pod *v1.Pod, containerID kubecontainer.ContainerID, logOptions *v1.PodLogOptions, stdout, stderr io.Writer, rawTerm bool) error

Temporarily export this function to share with dockershim. TODO: clean this up.

func GetFakeContainerID added in v1.6.0

func GetFakeContainerID(name string) string

GetFakeContainerID generates a fake container id from container name with a hash.

func GetImageRef added in v1.6.0

func GetImageRef(client DockerInterface, image string) (string, error)

GetImageRef returns the image digest if exists, or else returns the image ID. It is exported for reusing in dockershim.

func GetKubeletDockerContainers

func GetKubeletDockerContainers(client DockerInterface, allContainers bool) ([]*dockertypes.Container, error)

GetKubeletDockerContainers lists all container or just the running ones. Returns a list of docker containers that we manage

func GetSeccompOpts added in v1.5.0

func GetSeccompOpts(annotations map[string]string, ctrName, profileRoot string) ([]dockerOpt, error)

Temporarily export this function to share with dockershim. TODO: clean this up.

func GetUserFromImageUser added in v1.5.0

func GetUserFromImageUser(id string) string

GetUserFromImageUser splits the user out of an user:group string.

func IsContainerNotFoundError added in v1.6.0

func IsContainerNotFoundError(err error) bool

IsContainerNotFoundError checks whether the error is container not found error.

func IsImageNotFoundError added in v1.5.0

func IsImageNotFoundError(err error) bool

IsImageNotFoundError checks whether the error is image not found error. This is exposed to share with dockershim.

func LogSymlink(containerLogsDir, podFullName, containerName, dockerId string) string

func NewCalledDetail added in v1.5.0

func NewCalledDetail(name string, arguments []interface{}) calledDetail

NewCalledDetail create a new call detail item.

func NewContainerGC

func NewContainerGC(client DockerInterface, podGetter podGetter, network *knetwork.PluginManager, containerLogsDir string) *containerGC

func ParseDockerTimestamp

func ParseDockerTimestamp(s string) (time.Time, error)

ParseDockerTimestamp parses the timestamp returned by DockerInterface from string to time.Time

func PodInfraContainerEnv

func PodInfraContainerEnv(env map[string]string) kubecontainer.Option

func PortForward added in v1.5.0

func PortForward(client DockerInterface, podInfraContainerID string, port int32, stream io.ReadWriteCloser) error

Temporarily export this function to share with dockershim.

func RewriteResolvFile added in v1.6.0

func RewriteResolvFile(resolvFilePath string, dns []string, dnsSearch []string, useClusterFirstPolicy bool) error

RewriteResolvFile rewrites resolv.conf file generated by docker. Exported for reusing in dockershim.

func SetContainerNamePrefix

func SetContainerNamePrefix(prefix string)

SetContainerNamePrefix allows the container prefix name for this process to be changed. This is intended to support testing and bootstrapping experimentation. It cannot be changed once the Kubelet starts.

Types

type DockerInterface

type DockerInterface interface {
	ListContainers(options dockertypes.ContainerListOptions) ([]dockertypes.Container, error)
	InspectContainer(id string) (*dockertypes.ContainerJSON, error)
	CreateContainer(dockertypes.ContainerCreateConfig) (*dockertypes.ContainerCreateResponse, error)
	StartContainer(id string) error
	StopContainer(id string, timeout int) error
	RemoveContainer(id string, opts dockertypes.ContainerRemoveOptions) error
	InspectImageByRef(imageRef string) (*dockertypes.ImageInspect, error)
	InspectImageByID(imageID string) (*dockertypes.ImageInspect, error)
	ListImages(opts dockertypes.ImageListOptions) ([]dockertypes.Image, error)
	PullImage(image string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error
	RemoveImage(image string, opts dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDelete, error)
	ImageHistory(id string) ([]dockertypes.ImageHistory, error)
	Logs(string, dockertypes.ContainerLogsOptions, StreamOptions) error
	Version() (*dockertypes.Version, error)
	Info() (*dockertypes.Info, error)
	CreateExec(string, dockertypes.ExecConfig) (*dockertypes.ContainerExecCreateResponse, error)
	StartExec(string, dockertypes.ExecStartCheck, StreamOptions) error
	InspectExec(id string) (*dockertypes.ContainerExecInspect, error)
	AttachToContainer(string, dockertypes.ContainerAttachOptions, StreamOptions) error
	ResizeContainerTTY(id string, height, width int) error
	ResizeExecTTY(id string, height, width int) error
}

DockerInterface is an abstract interface for testability. It abstracts the interface of docker client.

func ConnectToDockerOrDie

func ConnectToDockerOrDie(dockerEndpoint string, requestTimeout, imagePullProgressDeadline time.Duration) DockerInterface

ConnectToDockerOrDie creates docker client connecting to docker daemon. If the endpoint passed in is "fake://", a fake docker client will be returned. The program exits if error occurs. The requestTimeout is the timeout for docker requests. If timeout is exceeded, the request will be cancelled and throw out an error. If requestTimeout is 0, a default value will be applied.

func NewInstrumentedDockerInterface

func NewInstrumentedDockerInterface(dockerClient DockerInterface) DockerInterface

Creates an instrumented DockerInterface from an existing DockerInterface.

type DockerManager

type DockerManager struct {
	// contains filtered or unexported fields
}

func NewDockerManager

func NewDockerManager(
	client DockerInterface,
	recorder record.EventRecorder,
	livenessManager proberesults.Manager,
	containerRefManager *kubecontainer.RefManager,
	podGetter podGetter,
	machineInfo *cadvisorapi.MachineInfo,
	podInfraContainerImage string,
	qps float32,
	burst int,
	containerLogsDir string,
	osInterface kubecontainer.OSInterface,
	networkPlugin knetwork.NetworkPlugin,
	runtimeHelper kubecontainer.RuntimeHelper,
	httpClient types.HttpGetter,
	execHandler ExecHandler,
	oomAdjuster *oom.OOMAdjuster,
	procFs procfs.ProcFSInterface,
	cpuCFSQuota bool,
	imageBackOff *flowcontrol.Backoff,
	serializeImagePulls bool,
	enableCustomMetrics bool,
	hairpinMode bool,
	seccompProfileRoot string,
	options ...kubecontainer.Option) *DockerManager

func NewFakeDockerManager

func NewFakeDockerManager(
	client DockerInterface,
	recorder record.EventRecorder,
	livenessManager proberesults.Manager,
	containerRefManager *kubecontainer.RefManager,
	machineInfo *cadvisorapi.MachineInfo,
	podInfraContainerImage string,
	qps float32,
	burst int,
	containerLogsDir string,
	osInterface kubecontainer.OSInterface,
	networkPlugin network.NetworkPlugin,
	runtimeHelper kubecontainer.RuntimeHelper,
	httpClient kubetypes.HttpGetter, imageBackOff *flowcontrol.Backoff) *DockerManager

func (*DockerManager) APIVersion

func (dm *DockerManager) APIVersion() (kubecontainer.Version, error)

func (*DockerManager) AttachContainer

func (dm *DockerManager) AttachContainer(containerID kubecontainer.ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error

func (*DockerManager) DeleteContainer

func (dm *DockerManager) DeleteContainer(containerID kubecontainer.ContainerID) error

func (*DockerManager) ExecInContainer

func (dm *DockerManager) ExecInContainer(containerID kubecontainer.ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error

ExecInContainer runs the command inside the container identified by containerID.

func (*DockerManager) GarbageCollect

func (dm *DockerManager) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool) error

Garbage collection of dead containers

func (*DockerManager) GetContainerLogs

func (dm *DockerManager) GetContainerLogs(pod *v1.Pod, containerID kubecontainer.ContainerID, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error

GetContainerLogs returns logs of a specific container. By default, it returns a snapshot of the container log. Set 'follow' to true to stream the log. Set 'follow' to false and specify the number of lines (e.g. "100" or "all") to tail the log. TODO: Make 'RawTerminal' option flagable.

func (*DockerManager) GetContainers

func (dm *DockerManager) GetContainers(all bool) ([]*kubecontainer.Container, error)

GetContainers returns a list of running containers if |all| is false; otherwise, it returns all containers.

func (*DockerManager) GetImageRef added in v1.6.0

func (dm *DockerManager) GetImageRef(image kubecontainer.ImageSpec) (string, error)

GetImageRef gets the reference (digest or ID) of the image which has already been in the local storage. It returns ("", nil) if the image isn't in the local storage.

func (*DockerManager) GetNetNS

func (dm *DockerManager) GetNetNS(containerID kubecontainer.ContainerID) (string, error)

GetNetNS returns the network namespace path for the given container

func (*DockerManager) GetPodContainerID

func (dm *DockerManager) GetPodContainerID(pod *kubecontainer.Pod) (kubecontainer.ContainerID, error)

func (*DockerManager) GetPodStatus

func (dm *DockerManager) GetPodStatus(uid kubetypes.UID, name, namespace string) (*kubecontainer.PodStatus, error)

func (*DockerManager) GetPods

func (dm *DockerManager) GetPods(all bool) ([]*kubecontainer.Pod, error)

func (DockerManager) ImageStats

func (isp DockerManager) ImageStats() (*runtime.ImageStats, error)

func (*DockerManager) KillContainerInPod

func (dm *DockerManager) KillContainerInPod(containerID kubecontainer.ContainerID, container *v1.Container, pod *v1.Pod, message string, gracePeriodOverride *int64) error

KillContainerInPod kills a container in the pod. It must be passed either a container ID or a container and pod, and will attempt to lookup the other information if missing.

func (*DockerManager) KillPod

func (dm *DockerManager) KillPod(pod *v1.Pod, runningPod kubecontainer.Pod, gracePeriodOverride *int64) error

TODO(random-liu): Change running pod to pod status in the future. We can't do it now, because kubelet also uses this function without pod status. We can only deprecate this after refactoring kubelet. TODO(random-liu): After using pod status for KillPod(), we can also remove the kubernetesPodLabel, because all the needed information should have been extract from new labels and stored in pod status. only hard eviction scenarios should provide a grace period override, all other code paths must pass nil.

func (*DockerManager) ListImages

func (dm *DockerManager) ListImages() ([]kubecontainer.Image, error)

List all images in the local storage.

func (*DockerManager) PortForward

func (dm *DockerManager) PortForward(pod *kubecontainer.Pod, port int32, stream io.ReadWriteCloser) error

PortForward executes socat in the pod's network namespace and copies data between stream (representing the user's local connection on their computer) and the specified port in the container.

TODO:

  • match cgroups of container
  • should we support nsenter + socat on the host? (current impl)
  • should we support nsenter + socat in a container, running with elevated privs and --pid=host?

func (*DockerManager) PullImage

func (dm *DockerManager) PullImage(image kubecontainer.ImageSpec, secrets []v1.Secret) (string, error)

PullImage pulls an image from network to local storage.

func (*DockerManager) RemoveImage

func (dm *DockerManager) RemoveImage(image kubecontainer.ImageSpec) error

Removes the specified image.

func (*DockerManager) Status

Status returns error if docker daemon is unhealthy, nil otherwise. Now we do this by checking whether: 1) `docker version` works 2) docker version is compatible with minimum requirement

func (*DockerManager) SyncPod

func (dm *DockerManager) SyncPod(pod *v1.Pod, _ v1.PodStatus, podStatus *kubecontainer.PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult)

Sync the running pod to match the specified desired pod.

func (*DockerManager) Type

func (dm *DockerManager) Type() string

func (*DockerManager) UpdatePodCIDR added in v1.5.0

func (dm *DockerManager) UpdatePodCIDR(podCIDR string) error

UpdatePodCIDR updates the podCIDR for the runtime. Currently no-ops, just implemented to satisfy the cri.

func (*DockerManager) Version

func (dm *DockerManager) Version() (kubecontainer.Version, error)

type DockerPuller

type DockerPuller interface {
	Pull(image string, secrets []v1.Secret) error
	GetImageRef(image string) (string, error)
}

DockerPuller is an abstract interface for testability. It abstracts image pull operations.

type ExecHandler

type ExecHandler interface {
	ExecInContainer(client DockerInterface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error
}

ExecHandler knows how to execute a command in a running Docker container.

type FakeContainer

type FakeContainer struct {
	ID         string
	Name       string
	Running    bool
	ExitCode   int
	Pid        int
	CreatedAt  time.Time
	StartedAt  time.Time
	FinishedAt time.Time
	Config     *dockercontainer.Config
	HostConfig *dockercontainer.HostConfig
}

Because the new data type returned by engine-api is too complex to manually initialize, we need a fake container which is easier to initialize.

type FakeDockerClient

type FakeDockerClient struct {
	sync.Mutex
	Clock                clock.Clock
	RunningContainerList []dockertypes.Container
	ExitedContainerList  []dockertypes.Container
	ContainerMap         map[string]*dockertypes.ContainerJSON
	ImageInspects        map[string]*dockertypes.ImageInspect
	Images               []dockertypes.Image
	Errors               map[string]error

	EnableTrace bool

	// Created, Started, Stopped and Removed all contain container docker ID
	Created []string
	Started []string
	Stopped []string
	Removed []string
	// Images pulled by ref (name or ID).
	ImagesPulled []string

	VersionInfo dockertypes.Version
	Information dockertypes.Info
	ExecInspect *dockertypes.ContainerExecInspect

	EnableSleep     bool
	ImageHistoryMap map[string][]dockertypes.ImageHistory
	// contains filtered or unexported fields
}

FakeDockerClient is a simple fake docker client, so that kubelet can be run for testing without requiring a real docker setup.

func NewFakeDockerClient

func NewFakeDockerClient() *FakeDockerClient

func (*FakeDockerClient) AssertCallDetails

func (f *FakeDockerClient) AssertCallDetails(calls ...calledDetail) (err error)

func (*FakeDockerClient) AssertCalls

func (f *FakeDockerClient) AssertCalls(calls []string) (err error)

func (*FakeDockerClient) AssertCreatedByName added in v1.6.0

func (f *FakeDockerClient) AssertCreatedByName(created []string) error

func (*FakeDockerClient) AssertCreatedByNameWithOrder added in v1.6.0

func (f *FakeDockerClient) AssertCreatedByNameWithOrder(created []string) error

func (*FakeDockerClient) AssertImagesPulled added in v1.6.0

func (f *FakeDockerClient) AssertImagesPulled(pulled []string) error

func (*FakeDockerClient) AssertStopped

func (f *FakeDockerClient) AssertStopped(stopped []string) error

func (*FakeDockerClient) AssertStoppedByName added in v1.6.0

func (f *FakeDockerClient) AssertStoppedByName(stopped []string) error

func (*FakeDockerClient) AttachToContainer

func (f *FakeDockerClient) AttachToContainer(id string, opts dockertypes.ContainerAttachOptions, sopts StreamOptions) error

func (*FakeDockerClient) ClearCalls

func (f *FakeDockerClient) ClearCalls()

func (*FakeDockerClient) ClearErrors

func (f *FakeDockerClient) ClearErrors()

func (*FakeDockerClient) CreateContainer

CreateContainer is a test-spy implementation of DockerInterface.CreateContainer. It adds an entry "create" to the internal method call record.

func (*FakeDockerClient) CreateExec

func (*FakeDockerClient) ImageHistory

func (f *FakeDockerClient) ImageHistory(id string) ([]dockertypes.ImageHistory, error)

func (*FakeDockerClient) Info

func (f *FakeDockerClient) Info() (*dockertypes.Info, error)

func (*FakeDockerClient) InjectError

func (f *FakeDockerClient) InjectError(fn string, err error)

func (*FakeDockerClient) InjectErrors

func (f *FakeDockerClient) InjectErrors(errs map[string]error)

func (*FakeDockerClient) InjectImageHistory

func (f *FakeDockerClient) InjectImageHistory(data map[string][]dockertypes.ImageHistory)

func (*FakeDockerClient) InjectImageInspects added in v1.6.0

func (f *FakeDockerClient) InjectImageInspects(inspects []dockertypes.ImageInspect)

func (*FakeDockerClient) InjectImages

func (f *FakeDockerClient) InjectImages(images []dockertypes.Image)

func (*FakeDockerClient) InspectContainer

func (f *FakeDockerClient) InspectContainer(id string) (*dockertypes.ContainerJSON, error)

InspectContainer is a test-spy implementation of DockerInterface.InspectContainer. It adds an entry "inspect" to the internal method call record.

func (*FakeDockerClient) InspectExec

func (*FakeDockerClient) InspectImageByID added in v1.5.0

func (f *FakeDockerClient) InspectImageByID(name string) (*dockertypes.ImageInspect, error)

InspectImageByID is a test-spy implementation of DockerInterface.InspectImageByID. It adds an entry "inspect" to the internal method call record.

func (*FakeDockerClient) InspectImageByRef added in v1.5.0

func (f *FakeDockerClient) InspectImageByRef(name string) (*dockertypes.ImageInspect, error)

InspectImageByRef is a test-spy implementation of DockerInterface.InspectImageByRef. It adds an entry "inspect" to the internal method call record.

func (*FakeDockerClient) ListContainers

ListContainers is a test-spy implementation of DockerInterface.ListContainers. It adds an entry "list" to the internal method call record.

func (*FakeDockerClient) ListImages

func (*FakeDockerClient) Logs

Logs is a test-spy implementation of DockerInterface.Logs. It adds an entry "logs" to the internal method call record.

func (*FakeDockerClient) PullImage

PullImage is a test-spy implementation of DockerInterface.PullImage. It adds an entry "pull" to the internal method call record.

func (*FakeDockerClient) RemoveContainer

func (f *FakeDockerClient) RemoveContainer(id string, opts dockertypes.ContainerRemoveOptions) error

func (*FakeDockerClient) RemoveImage

func (*FakeDockerClient) ResetImages added in v1.6.0

func (f *FakeDockerClient) ResetImages()

func (*FakeDockerClient) ResizeContainerTTY

func (f *FakeDockerClient) ResizeContainerTTY(id string, height, width int) error

func (*FakeDockerClient) ResizeExecTTY

func (f *FakeDockerClient) ResizeExecTTY(id string, height, width int) error

func (*FakeDockerClient) SetFakeContainers

func (f *FakeDockerClient) SetFakeContainers(containers []*FakeContainer)

func (*FakeDockerClient) SetFakeRunningContainers

func (f *FakeDockerClient) SetFakeRunningContainers(containers []*FakeContainer)

func (*FakeDockerClient) StartContainer

func (f *FakeDockerClient) StartContainer(id string) error

StartContainer is a test-spy implementation of DockerInterface.StartContainer. It adds an entry "start" to the internal method call record.

func (*FakeDockerClient) StartExec

func (f *FakeDockerClient) StartExec(startExec string, opts dockertypes.ExecStartCheck, sopts StreamOptions) error

func (*FakeDockerClient) StopContainer

func (f *FakeDockerClient) StopContainer(id string, timeout int) error

StopContainer is a test-spy implementation of DockerInterface.StopContainer. It adds an entry "stop" to the internal method call record.

func (*FakeDockerClient) Version

func (f *FakeDockerClient) Version() (*dockertypes.Version, error)

func (*FakeDockerClient) WithClock added in v1.6.0

func (f *FakeDockerClient) WithClock(c clock.Clock) *FakeDockerClient

func (*FakeDockerClient) WithTraceDisabled added in v1.6.0

func (f *FakeDockerClient) WithTraceDisabled() *FakeDockerClient

func (*FakeDockerClient) WithVersion added in v1.6.0

func (f *FakeDockerClient) WithVersion(version, apiVersion string) *FakeDockerClient

type FakeDockerPuller

type FakeDockerPuller struct {
	// contains filtered or unexported fields
}

FakeDockerPuller is meant to be a simple wrapper around FakeDockerClient. Please do not add more functionalities to it.

func (*FakeDockerPuller) GetImageRef added in v1.6.0

func (f *FakeDockerPuller) GetImageRef(image string) (string, error)

func (*FakeDockerPuller) Pull

func (f *FakeDockerPuller) Pull(image string, _ []v1.Secret) error

type ImageNotFoundError added in v1.6.0

type ImageNotFoundError struct {
	ID string
}

ImageNotFoundError is the error returned by InspectImage when image not found. Expose this to inject error in dockershim for testing.

func (ImageNotFoundError) Error added in v1.6.0

func (e ImageNotFoundError) Error() string

type KubeletContainerName

type KubeletContainerName struct {
	PodFullName   string
	PodUID        types.UID
	ContainerName string
}

KubeletContainerName encapsulates a pod name and a Kubernetes container name.

func ParseDockerName

func ParseDockerName(name string) (dockerName *KubeletContainerName, hash uint64, err error)

Unpacks a container name, returning the pod full name and container name we would have used to construct the docker name. If we are unable to parse the name, an error is returned.

type NativeExecHandler

type NativeExecHandler struct{}

NativeExecHandler executes commands in Docker containers using Docker's exec API.

func (*NativeExecHandler) ExecInContainer

func (*NativeExecHandler) ExecInContainer(client DockerInterface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error

type NsenterExecHandler

type NsenterExecHandler struct{}

NsenterExecHandler executes commands in Docker containers using nsenter.

func (*NsenterExecHandler) ExecInContainer

func (*NsenterExecHandler) ExecInContainer(client DockerInterface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error

TODO should we support nsenter in a container, running with elevated privs and --pid=host?

type StreamOptions

type StreamOptions struct {
	RawTerminal  bool
	InputStream  io.Reader
	OutputStream io.Writer
	ErrorStream  io.Writer
}

StreamOptions are the options used to configure the stream redirection

Directories

Path Synopsis
Package securitycontext contains security context api implementations
Package securitycontext contains security context api implementations

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL