fab

package
v0.13.2 Latest Latest
Warning

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

Go to latest
Published: Feb 29, 2024 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Index

Constants

View Source
const (
	PRESET_BM   cnc.Preset = "lab"
	PRESET_VLAB cnc.Preset = "vlab"
)
View Source
const (
	STAGE                   cnc.Stage = iota // Just a placeholder stage
	STAGE_INSTALL_0_PREP                     // Preparation for K3s and Zot installation
	STAGE_INSTALL_1_K3SZOT                   // Kube and Registry Installation, wait for registry available
	STAGE_INSTALL_2_MISC                     // Install misc services and wait for them to be ready
	STAGE_INSTALL_3_FABRIC                   // Install Fabric and wait for it to be ready
	STAGE_INSTALL_4_DASBOOT                  // Install Das Boot and wait for it to be ready
	STAGE_INSTALL_9_RELOADER

	STAGE_MAX // Keep it last so we can iterate over all stages
)

We expect services installed during the stage to be available at the end of it

View Source
const (
	OCI_REPO_CA_CN     = "OCI Repository CA"
	OCI_REPO_SERVER_CN = "localhost"

	KEY_USAGE_CA     = x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign
	KEY_USAGE_SERVER = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
)
View Source
const (
	FLATCAR_CONTROL_USER = "core"
	CONTROL_OS_IGNITION  = "ignition.json"
	DEFAULT_VLAB_SSH_KEY = "ssh-key"
)
View Source
const (
	FLAG_CATEGORY_CONFIG_BASE_SUFFIX = " fabricator options:"
)

Variables

View Source
var (
	HH_SUBNET                        = "172.30.0.0/16" // All Hedgehog Fabric IPs assignment will happen from this subnet
	CONTROL_KUBE_CLUSTER_CIDR        = "172.28.0.0/16"
	CONTROL_KUBE_SERVICE_CIDR        = "172.29.0.0/16"
	CONTROL_KUBE_CLUSTER_DNS         = "172.29.0.10"
	CONTROL_VIP                      = "172.30.1.1"
	CONTROL_VIP_MASK                 = "/32"
	ASN_SPINE                 uint32 = 65100
	ASN_LEAF_START            uint32 = 65101
	ZOT_CHECK_URL                    = fmt.Sprintf("https://%s:%d/v2/_catalog", CONTROL_VIP, ZOT_NODE_PORT)
	K3S_API_PORT                     = 6443
	ZOT_NODE_PORT                    = 31000
	DAS_BOOT_NTP_NODE_PORT           = 30123
	DAS_BOOT_SYSLOG_NODE_PORT        = 30514

	DEV_SSH_KEY     = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpF2+9I1Nj4BcN7y6DjzTbq1VcUYIRGyfzId5ZoBEFj" // 1P: Fabric Dev SSH Key Shared
	DEV_PASSWORD    = "$5$8nAYPGcl4l6G7Av1$Qi4/gnM0yPtGv9kjpMh78NuNSfQWy7vR1rulHpurL36"                  // 1P: Fabric Dev SONiC Admin
	DEV_SONIC_USERS = []meta.UserCreds{
		{
			Name:     "admin",
			Password: "$5$8nAYPGcl4l6G7Av1$Qi4/gnM0yPtGv9kjpMh78NuNSfQWy7vR1rulHpurL36",
			Role:     "admin",
		},
		{
			Name:     "op",
			Password: "$5$oj/NxDtFw3eTyini$VHwdjWXSNYRxlFMu.1S5ZlGJbUF/CGmCAZIBroJlax4",
			Role:     "operator",
		},
	}

	// Base
	REF_SOURCE           = cnc.Ref{Repo: "ghcr.io/githedgehog"}
	REF_TARGET           = cnc.Ref{Repo: fmt.Sprintf("%s:%d/githedgehog", CONTROL_VIP, ZOT_NODE_PORT)}
	REF_TARGET_INCLUSTER = REF_TARGET

	// K3s
	REF_K3S = cnc.Ref{Name: "k3s", Tag: "v1.29.1-k3s2"}

	// Zot
	REF_ZOT              = cnc.Ref{Name: "zot", Tag: "v1.4.3"}
	REF_ZOT_TARGET_IMAGE = cnc.Ref{Repo: "ghcr.io/project-zot", Name: "zot-minimal-linux-amd64"}

	// Das Boot
	DAS_BOOT_SEEDER_CLUSTER_IP = "172.29.42.42"

	REF_DASBOOT_VERSION       = cnc.Ref{Tag: "v0.11.4"}
	REF_DASBOOT_CRDS_CHART    = cnc.Ref{Name: "das-boot/charts/das-boot-crds"}
	REF_DASBOOT_SEEDER_CHART  = cnc.Ref{Name: "das-boot/charts/das-boot-seeder"}
	REF_DASBOOT_SEEDER_IMAGE  = cnc.Ref{Name: "das-boot/das-boot-seeder"}
	REF_DASBOOT_REGCTRL_CHART = cnc.Ref{Name: "das-boot/charts/das-boot-registration-controller"}
	REF_DASBOOT_REGCTRL_IMAGE = cnc.Ref{Name: "das-boot/das-boot-registration-controller"}

	REF_DASBOOT_RSYSLOG_CHART = cnc.Ref{Name: "das-boot/charts/rsyslog", Tag: "0.1.2"}
	REF_DASBOOT_RSYSLOG_IMAGE = cnc.Ref{Name: "das-boot/rsyslog", Tag: "0.1.0"}

	REF_DASBOOT_NTP_CHART = cnc.Ref{Name: "das-boot/charts/ntp", Tag: "0.0.3"}
	REF_DASBOOT_NTP_IMAGE = cnc.Ref{Name: "das-boot/ntp", Tag: "latest"}

	// ONIE
	REF_HONIE_VERSION         = cnc.Ref{Tag: "0.1.3"}
	REF_ONIE_TARGET_VERSION   = cnc.Ref{Tag: "latest"} // the target tag currently *must* always be "latest" as this is hardcoded in DAS BOOT
	REF_ONIE_SRCTARGETS_PAIRS = []struct {
		src     cnc.Ref
		targets []cnc.Ref
	}{
		{
			// contains filtered or unexported fields
		},

		{
			// contains filtered or unexported fields
		},
		{
			// contains filtered or unexported fields
		},
		{
			// contains filtered or unexported fields
		},

		{
			// contains filtered or unexported fields
		},
	}

	// SONiC
	REF_SONIC_BCOM_BASE   = cnc.Ref{Name: "sonic-bcom-private", Tag: "base-bin-4.2.0"}
	REF_SONIC_BCOM_CAMPUS = cnc.Ref{Name: "sonic-bcom-private", Tag: "campus-bin-4.2.0"}
	REF_SONIC_BCOM_VS     = cnc.Ref{Name: "sonic-bcom-private", Tag: "vs-bin-4.2.0"}

	REF_SONIC_TARGET_VERSION = cnc.Ref{Tag: "latest"}
	REF_SONIC_TARGETS_BASE   = []cnc.Ref{
		{Name: "sonic/x86_64-dellemc_s5248f_c3538-r0"},
		{Name: "sonic/x86_64-dellemc_s5232f_c3538-r0"},
		{Name: "sonic/x86_64-cel_questone_2-r0"},
		{Name: "sonic/x86_64-cel_seastone_2-r0"},
		{Name: "sonic/x86_64-cel_silverstone-r0"},
		{Name: "sonic/x86_64-accton_as7726_32x-r0"},
		{Name: "sonic/x86_64-accton_as7326_56x-r0"},
		{Name: "sonic/x86_64-accton_as7712_32x-r0"},
	}
	REF_SONIC_TARGETS_CAMPUS = []cnc.Ref{
		{Name: "sonic/x86_64-accton_as4630_54npe-r0"},
	}
	REF_SONIC_TARGETS_VS = []cnc.Ref{
		{Name: "sonic/x86_64-kvm_x86_64-r0"},
	}

	// Fabric
	REF_FABRIC_VERSION           = cnc.Ref{Tag: "v0.31.1"}
	REF_FABRIC_API_CHART         = cnc.Ref{Name: "fabric/charts/fabric-api"}
	REF_FABRIC_CHART             = cnc.Ref{Name: "fabric/charts/fabric"}
	REF_FABRIC_IMAGE             = cnc.Ref{Name: "fabric/fabric"}
	REF_FABRIC_AGENT             = cnc.Ref{Name: "fabric/agent"}
	REF_FABRIC_CONTROL_AGENT     = cnc.Ref{Name: "fabric/agent"}
	REF_FABRIC_CTL               = cnc.Ref{Name: "fabric/hhfctl"}
	REF_FABRIC_DHCP_SERVER       = cnc.Ref{Name: "fabric/fabric-dhcp-server"}
	REF_FABRIC_DHCP_SERVER_CHART = cnc.Ref{Name: "fabric/charts/fabric-dhcp-server"}
	REF_FABRIC_DHCPD             = cnc.Ref{Name: "fabric/fabric-dhcpd"}
	REF_FABRIC_DHCPD_CHART       = cnc.Ref{Name: "fabric/charts/fabric-dhcpd"}

	// Misc
	REF_K9S        = cnc.Ref{Name: "fabricator/k9s", Tag: "v0.31.7"}
	REF_RBAC_PROXY = cnc.Ref{Name: "fabricator/kube-rbac-proxy", Tag: "v0.14.1"}
	REF_TOOLBOX    = cnc.Ref{Name: "fabricator/toolbox", Tag: "latest"}

	// Cert manager
	REF_CERT_MANAGER_VERSION    = cnc.Ref{Tag: "v1.13.0"}
	REF_CERT_MANAGER_CAINJECTOR = cnc.Ref{Name: "fabricator/cert-manager-cainjector"}
	REF_CERT_MANAGER_CONTROLLER = cnc.Ref{Name: "fabricator/cert-manager-controller"}
	REF_CERT_MANAGER_ACMESOLVER = cnc.Ref{Name: "fabricator/cert-manager-acmesolver"}
	REF_CERT_MANAGER_WEBHOOK    = cnc.Ref{Name: "fabricator/cert-manager-webhook"}
	REF_CERT_MANAGER_CTL        = cnc.Ref{Name: "fabricator/cert-manager-ctl"}
	REF_CERT_MANAGER_CHART      = cnc.Ref{Name: "fabricator/charts/cert-manager"}

	// Reloader
	REF_MISC_RELOADER       = cnc.Ref{Name: "fabricator/reloader", Tag: "v1.0.40"}
	REF_MISC_RELOADER_CHART = cnc.Ref{Name: "fabricator/charts/reloader", Tag: "1.0.40"}

	// VLAB
	REF_VLAB_ONIE        = cnc.Ref{Name: "honie", Tag: "lldp"}
	REF_VLAB_FLATCAR     = cnc.Ref{Name: "flatcar", Tag: "3760.2.0"}
	REF_VLAB_EEPROM_EDIT = cnc.Ref{Name: "onie-qcow2-eeprom-edit", Tag: "latest"}
)
View Source
var (
	BundleControlInstall = cnc.Bundle{
		Name:        "control-install",
		IsInstaller: true,
	}
	BundleControlOS = cnc.Bundle{
		Name: "control-os",
	}
	BundleServerInstall = cnc.Bundle{
		Name:        "server-install",
		IsInstaller: true,
	}
	BundleServerOS = cnc.Bundle{
		Name: "server-os",
	}
	BundleVlabFiles = cnc.Bundle{
		Name: "vlab-files",
	}
	BundleVlabVMs = cnc.Bundle{
		Name: "vlab-vms",
	}
	BundleHlabFiles = cnc.Bundle{
		Name: "hlab-files",
	}
)

Functions

func LoadVLAB

func LoadVLAB(basedir string, mngr *cnc.Manager, dryRun bool, size string, restrictServers bool) (*vlab.Service, error)

func NewCNCManager

func NewCNCManager() *cnc.Manager

Types

type Base

type Base struct {
	cnc.NoValidationComponent

	Source          cnc.Ref  `json:"source,omitempty"`
	Target          cnc.Ref  `json:"target,omitempty"`
	TargetInCluster cnc.Ref  `json:"targetInCluster,omitempty"`
	AuthorizedKeys  []string `json:"authorizedKeys,omitempty"`
	Dev             bool     `json:"dev,omitempty"`
	// contains filtered or unexported fields
}

func BaseConfig

func BaseConfig(get cnc.GetComponent) *Base

func (*Base) Build

func (cfg *Base) Build(basedir string, preset cnc.Preset, fabricMode meta.FabricMode, get cnc.GetComponent, wiring *wiring.Data, run cnc.AddBuildOp, install cnc.AddRunOp) error

func (*Base) Flags

func (cfg *Base) Flags() []cli.Flag

func (*Base) Hydrate

func (cfg *Base) Hydrate(preset cnc.Preset, fabricMode meta.FabricMode) error

func (*Base) IsEnabled

func (cfg *Base) IsEnabled(preset cnc.Preset) bool

func (*Base) Name

func (cfg *Base) Name() string

type ControlOS

type ControlOS struct {
	cnc.NoValidationComponent

	PasswordHash string `json:"passwordHash,omitempty"`
}

func (*ControlOS) Build

func (cfg *ControlOS) Build(basedir string, preset cnc.Preset, fabricMode meta.FabricMode, get cnc.GetComponent, data *wiring.Data, run cnc.AddBuildOp, install cnc.AddRunOp) error

func (*ControlOS) Flags

func (cfg *ControlOS) Flags() []cli.Flag

func (*ControlOS) Hydrate

func (cfg *ControlOS) Hydrate(preset cnc.Preset, fabricMode meta.FabricMode) error

func (*ControlOS) IsEnabled

func (cfg *ControlOS) IsEnabled(preset cnc.Preset) bool

func (*ControlOS) Name

func (cfg *ControlOS) Name() string

type DasBoot

type DasBoot struct {
	cnc.NoValidationComponent

	Ref             cnc.Ref    `json:"ref,omitempty"`
	RsyslogChartRef cnc.Ref    `json:"rsyslogChartRef,omitempty"`
	RsyslogImageRef cnc.Ref    `json:"rsyslogImageRef,omitempty"`
	NTPChartRef     cnc.Ref    `json:"ntpChartRef,omitempty"`
	NTPImageRef     cnc.Ref    `json:"ntpImageRef,omitempty"`
	CRDsChartRef    cnc.Ref    `json:"crdsChartRef,omitempty"`
	SeederChartRef  cnc.Ref    `json:"seederChartRef,omitempty"`
	SeederImageRef  cnc.Ref    `json:"seederImageRef,omitempty"`
	RegCtrlChartRef cnc.Ref    `json:"regCtrlChartRef,omitempty"`
	RegCtrlImageRef cnc.Ref    `json:"regCtrlImageRef,omitempty"`
	SONiCBaseRef    cnc.Ref    `json:"sonicBaseRef,omitempty"`
	SONiCCampusRef  cnc.Ref    `json:"sonicCampusRef,omitempty"`
	SONiCVSRef      cnc.Ref    `json:"sonicVSRef,omitempty"`
	TLS             DasBootTLS `json:"tls,omitempty"`
	ClusterIP       string     `json:"clusterIP,omitempty"`
	NTPServers      string     `json:"ntpServers,omitempty"`
}

func (*DasBoot) Build

func (cfg *DasBoot) Build(basedir string, preset cnc.Preset, fabricMode meta.FabricMode, get cnc.GetComponent, wiring *wiring.Data, run cnc.AddBuildOp, install cnc.AddRunOp) error

func (*DasBoot) Flags

func (cfg *DasBoot) Flags() []cli.Flag

func (*DasBoot) Hydrate

func (cfg *DasBoot) Hydrate(preset cnc.Preset, fabricMode meta.FabricMode) error

func (*DasBoot) IsEnabled

func (cfg *DasBoot) IsEnabled(preset cnc.Preset) bool

func (*DasBoot) Name

func (cfg *DasBoot) Name() string

type DasBootTLS

type DasBootTLS struct {
	ServerCA cnc.KeyPair `json:"serverCA,omitempty"`
	Server   cnc.KeyPair `json:"server,omitempty"`
	ClientCA cnc.KeyPair `json:"clientCA,omitempty"`
	ConfigCA cnc.KeyPair `json:"configCA,omitempty"`
	Config   cnc.KeyPair `json:"config,omitempty"`
}

type Fabric

type Fabric struct {
	Ref                      cnc.Ref `json:"ref,omitempty"`
	FabricApiChartRef        cnc.Ref `json:"fabricApiChartRef,omitempty"`
	FabricChartRef           cnc.Ref `json:"fabricChartRef,omitempty"`
	FabricImageRef           cnc.Ref `json:"fabricImageRef,omitempty"`
	AgentRef                 cnc.Ref `json:"agentRef,omitempty"`
	ControlAgentRef          cnc.Ref `json:"controlAgentRef,omitempty"`
	CtlRef                   cnc.Ref `json:"ctlRef,omitempty"`
	FabricDHCPServerRef      cnc.Ref `json:"dhcpServerRef,omitempty"`
	FabricDHCPServerChartRef cnc.Ref `json:"dhcpServerChartRef,omitempty"`
	FabricDHCPDRef           cnc.Ref `json:"dhcpdRef,omitempty"`
	FabricDHCPDChartRef      cnc.Ref `json:"dhcpdChartRef,omitempty"`
	BaseVPCCommunity         string  `json:"baseVPCCommunity,omitempty"`
	ServerFacingMTUOffset    uint    `json:"serverFacingMTUOffset,omitempty"`
	DHCPServer               string  `json:"dhcpServer,omitempty"`
}

func (*Fabric) Build

func (cfg *Fabric) Build(basedir string, preset cnc.Preset, fabricMode meta.FabricMode, get cnc.GetComponent, wiring *wiringlib.Data, run cnc.AddBuildOp, install cnc.AddRunOp) error

func (*Fabric) Flags

func (cfg *Fabric) Flags() []cli.Flag

func (*Fabric) Hydrate

func (cfg *Fabric) Hydrate(preset cnc.Preset, fabricMode meta.FabricMode) error

func (*Fabric) IsEnabled

func (cfg *Fabric) IsEnabled(preset cnc.Preset) bool

func (*Fabric) Name

func (cfg *Fabric) Name() string

func (*Fabric) Validate added in v0.13.0

func (cfg *Fabric) Validate(basedir string, preset cnc.Preset, fabricMode meta.FabricMode, get cnc.GetComponent, wiring *wiringlib.Data) error

type K3s

type K3s struct {
	cnc.NoValidationComponent

	Ref         cnc.Ref  `json:"ref,omitempty"`
	ClusterCIDR string   `json:"clusterCIDR,omitempty"`
	ServiceCIDR string   `json:"serviceCIDR,omitempty"`
	ClusterDNS  string   `json:"clusterDNS,omitempty"`
	TLSSAN      []string `json:"tlsSAN,omitempty"`
	// contains filtered or unexported fields
}

func K3sConfig added in v0.6.0

func K3sConfig(get cnc.GetComponent) *K3s

func (*K3s) Build

func (cfg *K3s) Build(basedir string, preset cnc.Preset, fabricMode meta.FabricMode, get cnc.GetComponent, wiring *wiring.Data, run cnc.AddBuildOp, install cnc.AddRunOp) error

func (*K3s) ControlNodeName

func (cfg *K3s) ControlNodeName(data *wiring.Data) (string, error)

func (*K3s) Flags

func (cfg *K3s) Flags() []cli.Flag

func (*K3s) Hydrate

func (cfg *K3s) Hydrate(preset cnc.Preset, fabricMode meta.FabricMode) error

func (*K3s) IsEnabled

func (cfg *K3s) IsEnabled(preset cnc.Preset) bool

func (*K3s) Name

func (cfg *K3s) Name() string

type Misc

type Misc struct {
	cnc.NoValidationComponent

	K9sRef                   cnc.Ref `json:"k9sRef,omitempty"`
	RBACProxyImageRef        cnc.Ref `json:"rbacProxyRef,omitempty"`
	CertManagerRef           cnc.Ref `json:"certManagerRef,omitempty"`
	CertManagerCAInjectorRef cnc.Ref `json:"certManagerCAInjectorRef,omitempty"`
	CertManagerControllerRef cnc.Ref `json:"certManagerControllerRef,omitempty"`
	CertManagerAcmeSolverRef cnc.Ref `json:"certManagerAcmeSolverRef,omitempty"`
	CertManagerWebhookRef    cnc.Ref `json:"certManagerWebhookRef,omitempty"`
	CertManagerCtlRef        cnc.Ref `json:"certManagerCtlRef,omitempty"`
	CertManagerChartRef      cnc.Ref `json:"certManagerChartRef,omitempty"`
	ReloaderImageRef         cnc.Ref `json:"reloaderImageRef,omitempty"`
	ReloaderChartRef         cnc.Ref `json:"reloaderChartRef,omitempty"`
}

func MiscConfig

func MiscConfig(get cnc.GetComponent) *Misc

func (*Misc) Build

func (cfg *Misc) Build(basedir string, preset cnc.Preset, fabricMode meta.FabricMode, get cnc.GetComponent, wiring *wiring.Data, run cnc.AddBuildOp, install cnc.AddRunOp) error

func (*Misc) Flags

func (cfg *Misc) Flags() []cli.Flag

func (*Misc) Hydrate

func (cfg *Misc) Hydrate(preset cnc.Preset, fabricMode meta.FabricMode) error

func (*Misc) IsEnabled

func (cfg *Misc) IsEnabled(preset cnc.Preset) bool

func (*Misc) Name

func (cfg *Misc) Name() string

type ServerOS added in v0.5.3

type ServerOS struct {
	cnc.NoValidationComponent

	PasswordHash string  `json:"passwordHash,omitempty"`
	ToolboxRef   cnc.Ref `json:"toolboxRef,omitempty"`
}

func (*ServerOS) Build added in v0.5.3

func (cfg *ServerOS) Build(basedir string, preset cnc.Preset, fabricMode meta.FabricMode, get cnc.GetComponent, data *wiring.Data, run cnc.AddBuildOp, install cnc.AddRunOp) error

func (*ServerOS) Flags added in v0.5.3

func (cfg *ServerOS) Flags() []cli.Flag

func (*ServerOS) Hydrate added in v0.5.3

func (cfg *ServerOS) Hydrate(preset cnc.Preset, fabricMode meta.FabricMode) error

func (*ServerOS) IsEnabled added in v0.5.3

func (cfg *ServerOS) IsEnabled(preset cnc.Preset) bool

func (*ServerOS) Name added in v0.5.3

func (cfg *ServerOS) Name() string

type VLAB

type VLAB struct {
	cnc.NoValidationComponent

	ONIERef       cnc.Ref `json:"onieRef,omitempty"`
	FlatcarRef    cnc.Ref `json:"flatcarRef,omitempty"`
	EEPROMEditRef cnc.Ref `json:"eepromEditRef,omitempty"`
}

func (*VLAB) Build

func (cfg *VLAB) Build(basedir string, preset cnc.Preset, fabricMode meta.FabricMode, get cnc.GetComponent, data *wiring.Data, run cnc.AddBuildOp, install cnc.AddRunOp) error

func (*VLAB) Flags

func (cfg *VLAB) Flags() []cli.Flag

func (*VLAB) Hydrate

func (cfg *VLAB) Hydrate(preset cnc.Preset, fabricMode meta.FabricMode) error

func (*VLAB) IsEnabled

func (cfg *VLAB) IsEnabled(preset cnc.Preset) bool

func (*VLAB) Name

func (cfg *VLAB) Name() string

type Zot

type Zot struct {
	cnc.NoValidationComponent

	Ref cnc.Ref `json:"ref,omitempty"`
	TLS ZotTLS  `json:"tls,omitempty"`
}

func ZotConfig

func ZotConfig(get cnc.GetComponent) *Zot

func (*Zot) Build

func (cfg *Zot) Build(basedir string, preset cnc.Preset, fabricMode meta.FabricMode, get cnc.GetComponent, wiring *wiring.Data, run cnc.AddBuildOp, install cnc.AddRunOp) error

func (*Zot) Flags

func (cfg *Zot) Flags() []cli.Flag

func (*Zot) Hydrate

func (cfg *Zot) Hydrate(preset cnc.Preset, fabricMode meta.FabricMode) error

func (*Zot) IsEnabled

func (cfg *Zot) IsEnabled(preset cnc.Preset) bool

func (*Zot) Name

func (cfg *Zot) Name() string

type ZotTLS

type ZotTLS struct {
	CA     cnc.KeyPair `json:"ca,omitempty"`
	Server cnc.KeyPair `json:"server,omitempty"`
}

Directories

Path Synopsis
cnc
bin

Jump to

Keyboard shortcuts

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