dockerimage

package
v0.0.0-...-5f60744 Latest Latest
Warning

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

Go to latest
Published: Nov 18, 2023 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ContentTypeUTF8   = "utf8"
	ContentTypeBinary = "binary"
)
View Source
const CDMDumpToConsole = "console"
View Source
const TypeLayers = "layers"
View Source
const WhiteoutLinkDir = WhiteoutMetaPrefix + "plnk"

WhiteoutLinkDir is a directory AUFS uses for storing hardlink links to other layers. Normally these should not go into exported archives and all changed hardlinks should be copied to the top layer.

View Source
const WhiteoutMetaPrefix = WhiteoutPrefix + WhiteoutPrefix

WhiteoutMetaPrefix prefix means whiteout has a special meaning and is not for removing an actual file. Normally these files are excluded from exported archives.

View Source
const WhiteoutOpaqueDir = WhiteoutMetaPrefix + ".opq"

WhiteoutOpaqueDir file means directory has been made opaque - meaning readdir calls to this directory do not follow to lower layers.

View Source
const WhiteoutPrefix = ".wh."

WhiteoutPrefix prefix means file is a whiteout. If this is followed by a filename this means that file has been removed from the base layer.

Variables

This section is empty.

Functions

func FileDataFromTar

func FileDataFromTar(tarPath, filePath string) ([]byte, error)

func FileReaderFromTar

func FileReaderFromTar(tarPath, filePath string) (io.ReadCloser, error)

func IsDeletedFileObject

func IsDeletedFileObject(path string) bool

func IsLayerMediaType

func IsLayerMediaType(value types.MediaType) bool

func NormalizeFileObjectLayerPath

func NormalizeFileObjectLayerPath(path string) (string, bool, bool, error)

Types

type BuildKitBuildInfo

type BuildKitBuildInfo struct {
	// Frontend defines the frontend used to build.
	Frontend string `json:"frontend,omitempty"`
	// Attrs defines build request attributes.
	Attrs map[string]*string `json:"attrs,omitempty"`
	// Sources defines build dependencies.
	Sources []*BuildSource `json:"sources,omitempty"`
	// Deps defines context dependencies.
	Deps map[string]BuildKitBuildInfo `json:"deps,omitempty"`
}

type BuildSource

type BuildSource struct {
	// Type defines the SourceType source type (docker-image, git, http).
	Type SourceType `json:"type,omitempty"`
	// Ref is the reference of the source.
	Ref string `json:"ref,omitempty"`
	// Alias is a special field used to match with the actual source ref
	// because frontend might have already transformed a string user typed
	// before generating LLB.
	Alias string `json:"alias,omitempty"`
	// Pin is the source digest.
	Pin string `json:"pin,omitempty"`
}

type CertsInfo

type CertsInfo struct {
	Bundles         []string          `json:"bundles,omitempty"`
	Files           []string          `json:"files,omitempty"`
	Links           map[string]string `json:"links,omitempty"`
	Hashes          map[string]string `json:"hashes,omitempty"`
	PrivateKeys     []string          `json:"private_keys,omitempty"`
	PrivateKeyLinks map[string]string `json:"private_key_links,omitempty"`
}

type CertsRefInfo

type CertsRefInfo struct {
	Bundles         map[string]struct{} `json:"bundles,omitempty"`
	Files           map[string]struct{} `json:"files,omitempty"`
	Links           map[string]string   `json:"links,omitempty"`
	Hashes          map[string]string   `json:"hashes,omitempty"`
	PrivateKeys     map[string]struct{} `json:"private_keys,omitempty"`
	PrivateKeyLinks map[string]string   `json:"private_key_links,omitempty"`
}

type ChangeDataHashMatcher

type ChangeDataHashMatcher struct {
	Dump        bool
	DumpConsole bool
	DumpDir     string
	Hash        string //lowercase
}

type ChangeDataMatcher

type ChangeDataMatcher struct {
	Dump        bool
	DumpConsole bool
	DumpDir     string
	PathPattern string
	DataPattern string
	Matcher     *regexp.Regexp
}

type ChangeInfo

type ChangeInfo struct {
	Layer  int             `json:"layer"`
	Object *ObjectMetadata `json:"-"`
}

type ChangePathMatcher

type ChangePathMatcher struct {
	Dump        bool
	DumpConsole bool
	DumpDir     string
	PathPattern string
}

type ChangeType

type ChangeType int
const (
	ChangeUnknown ChangeType = iota
	ChangeDelete
	ChangeAdd
	ChangeModify
)

func (ChangeType) MarshalJSON

func (ct ChangeType) MarshalJSON() ([]byte, error)

func (ChangeType) String

func (ct ChangeType) String() string

func (*ChangeType) UnmarshalJSON

func (ct *ChangeType) UnmarshalJSON(b []byte) error

type Changeset

type Changeset struct {
	Deleted           []int
	DeletedDirContent []int
	Added             []int
	Modified          []int
}

type ChangesetSummary

type ChangesetSummary struct {
	Deleted  uint64 `json:"deleted"`
	Added    uint64 `json:"added"`
	Modified uint64 `json:"modified"`
}

type ConfigObject

type ConfigObject struct {
	V1ConfigObject
	Parent     string     `json:"parent,omitempty"` //nolint:govet
	RootFS     *RootFS    `json:"rootfs,omitempty"`
	History    []XHistory `json:"history,omitempty"`
	OSVersion  string     `json:"os.version,omitempty"`
	OSFeatures []string   `json:"os.features,omitempty"`

	//buildkit build info
	BuildInfoRaw     string `json:"moby.buildkit.buildinfo.v1,omitempty"`
	BuildInfoDecoded *BuildKitBuildInfo
}

func LoadConfigObject

func LoadConfigObject(archivePath, imageID string) (*ConfigObject, error)

type ContainerConfig

type ContainerConfig struct {
	Hostname        string              // Hostname
	Domainname      string              // Domainname
	User            string              // User that will run the command(s) inside the container, also support user:group
	AttachStdin     bool                // Attach the standard input, makes possible user interaction
	AttachStdout    bool                // Attach the standard output
	AttachStderr    bool                // Attach the standard error
	ExposedPorts    map[string]struct{} `json:",omitempty"` // List of exposed ports
	Tty             bool                // Attach standard streams to a tty, including stdin if it is not closed.
	OpenStdin       bool                // Open stdin
	StdinOnce       bool                // If true, close stdin after the 1 attached client disconnects.
	Env             []string            // List of environment variable to set in the container
	Cmd             []string            // Command to run when starting the container
	Healthcheck     *HealthConfig       `json:",omitempty"` // Healthcheck describes how to check the container is healthy
	ArgsEscaped     bool                `json:",omitempty"` // True if command is already escaped (meaning treat as a command line) (Windows specific).
	Image           string              // Name of the image as it was passed by the operator (e.g. could be symbolic)
	Volumes         map[string]struct{} // List of volumes (mounts) used for the container
	WorkingDir      string              // Current directory (PWD) in the command will be launched
	Entrypoint      []string            // Entrypoint to run when starting the container
	NetworkDisabled bool                `json:",omitempty"` // Is network disabled
	MacAddress      string              `json:",omitempty"` // Mac Address of the container
	OnBuild         []string            // ONBUILD metadata that were defined on the image Dockerfile
	Labels          map[string]string   // List of labels set to this container
	StopSignal      string              `json:",omitempty"` // Signal to stop a container
	StopTimeout     *int                `json:",omitempty"` // Timeout (in seconds) to stop a container
	Shell           []string            `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT
}

type DetectOpParam

type DetectOpParam struct {
	/// Operation is enabled
	Enabled bool
	/// Dump/save raw data
	DumpRaw bool
	/// Dump raw data to console
	IsConsoleOut bool
	/// Dump raw data to directory (otherwise save to an archive file)
	IsDirOut bool
	/// Output path (directory or archive path)
	OutputPath string
	/// Input parameters for the operation
	InputParams map[string]string
}

type DuplicateFilesReport

type DuplicateFilesReport struct {
	FileCount   uint64         `json:"file_count"`
	FileSize    uint64         `json:"file_size"`
	AllFileSize uint64         `json:"all_file_size"`
	WastedSize  uint64         `json:"wasted_size"`
	Files       map[string]int `json:"files"`
}

type FileMetadata

type FileMetadata struct {
	Type       string      `json:"type,omitempty"`
	IsDir      bool        `json:"is_dir,omitempty"`
	IsDelete   bool        `json:"is_delete,omitempty"`
	IsOpq      bool        `json:"is_opq,omitempty"`
	Name       string      `json:"name,omitempty"`
	RawName    string      `json:"raw_name,omitempty"`
	Size       int64       `json:"size,omitempty"`
	Mode       os.FileMode `json:"mode,omitempty"`
	UID        int         `json:"uid"`
	GID        int         `json:"gid"`
	ModTime    time.Time   `json:"mod_time,omitempty"`
	ChangeTime time.Time   `json:"change_time,omitempty"`
}

type FileSelector

type FileSelector struct {
	Type              FileSelectorType
	Key               string
	IndexKey          int
	IndexEndKey       int
	ReverseIndexRange bool
	RawNames          bool
	NoDirs            bool
	Deleted           bool
}

type FileSelectorType

type FileSelectorType string
const (
	FSTAll        FileSelectorType = "fst.all"
	FSTDigest     FileSelectorType = "fst.digest"
	FSTDiffID     FileSelectorType = "fst.diffid"
	FSTIndex      FileSelectorType = "fst.index"
	FSTIndexRange FileSelectorType = "fst.index.range"
)

type HealthConfig

type HealthConfig struct {
	// Test is the test to perform to check that the container is healthy.
	// An empty slice means to inherit the default.
	// The options are:
	// {} : inherit healthcheck
	// {"NONE"} : disable healthcheck
	// {"CMD", args...} : exec arguments directly
	// {"CMD-SHELL", command} : run command with system's default shell
	Test []string `json:",omitempty"`

	// Zero means to inherit. Durations are expressed as integer nanoseconds.
	Interval    time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
	Timeout     time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
	StartPeriod time.Duration `json:",omitempty"` // The start period for the container to initialize before the retries starts to count down.

	// Retries is the number of consecutive failures needed to consider a container as unhealthy.
	// Zero means inherit.
	Retries int `json:",omitempty"`
}

type ImageReport

type ImageReport struct {
	Stats        PackageStats                     `json:"stats"`
	Duplicates   map[string]*DuplicateFilesReport `json:"duplicates,omitempty"`
	SpecialPerms *SpecialPermsInfo                `json:"special_perms,omitempty"`
	OSShells     []*system.OSShell                `json:"shells,omitempty"`
	Certs        CertsInfo                        `json:"certs"`
	CACerts      CertsInfo                        `json:"ca_certs"`
	BuildInfo    *BuildKitBuildInfo               `json:"build_info,omitempty"`
	Identities   *sysidentity.Report              `json:"identities,omitempty"`
}

type InstructionSummary

type InstructionSummary struct {
	Index      int    `json:"index"`
	ImageIndex int    `json:"image_index"`
	Type       string `json:"type"`
	All        string `json:"all"`
	Snippet    string `json:"snippet"`
}

type Layer

type Layer struct {
	ID                  string
	Index               int
	Path                string
	LayerDataSource     string
	MetadataChangesOnly bool
	FSDiffID            string
	Stats               LayerStats
	Changes             Changeset
	Objects             []*ObjectMetadata
	References          map[string]*ObjectMetadata
	Top                 TopObjects
	Distro              *system.DistroInfo
	DataMatches         map[string][]*ChangeDataMatcher   //object.Name -> matched CDM
	DataHashMatches     map[string]*ChangeDataHashMatcher //object.Name -> matched CDHM
	// contains filtered or unexported fields
}

func LoadLayer

func LoadLayer(archivePath, imageID, layerID string) (*Layer, error)

func (*Layer) HasMatches

func (ref *Layer) HasMatches() bool

type LayerFiles

type LayerFiles struct {
	Layer *LayerMetadata  `json:"layer"`
	Files []*FileMetadata `json:"files"`
}

type LayerMetadata

type LayerMetadata struct {
	Index     int    `json:"index,omitempty"`
	Digest    string `json:"digest,omitempty"`
	DiffID    string `json:"diff_id,omitempty"`
	MediaType string `json:"media_type,omitempty"`
	Size      int64  `json:"size,omitempty"`
}

type LayerReport

type LayerReport struct {
	ID                  string                `json:"id"`
	Index               int                   `json:"index"`
	Path                string                `json:"path,omitempty"`
	LayerDataSource     string                `json:"layer_data_source,omitempty"`
	MetadataChangesOnly bool                  `json:"metadata_changes_only,omitempty"`
	FSDiffID            string                `json:"fsdiff_id,omitempty"`
	Stats               LayerStats            `json:"stats"`
	Changes             ChangesetSummary      `json:"changes"`
	Top                 []*ObjectMetadata     `json:"top"`
	Deleted             []*ObjectMetadata     `json:"deleted,omitempty"`
	Added               []*ObjectMetadata     `json:"added,omitempty"`
	Modified            []*ObjectMetadata     `json:"modified,omitempty"`
	ChangeInstruction   *InstructionSummary   `json:"change_instruction,omitempty"`
	OtherInstructions   []*InstructionSummary `json:"other_instructions,omitempty"`
}

type LayerStats

type LayerStats struct {
	//BlobSize         uint64 `json:"blob_size"`
	AllSize                uint64 `json:"all_size"`
	ObjectCount            uint64 `json:"object_count"`
	DirCount               uint64 `json:"dir_count"`
	FileCount              uint64 `json:"file_count"`
	LinkCount              uint64 `json:"link_count"`
	MaxFileSize            uint64 `json:"max_file_size"`
	MaxDirSize             uint64 `json:"max_dir_size"`
	DeletedCount           uint64 `json:"deleted_count"`
	DeletedDirContentCount uint64 `json:"deleted_dir_content_count"`
	DeletedDirCount        uint64 `json:"deleted_dir_count"`
	DeletedFileCount       uint64 `json:"deleted_file_count"`
	DeletedLinkCount       uint64 `json:"deleted_link_count"`
	DeletedSize            uint64 `json:"deleted_size"`
	AddedSize              uint64 `json:"added_size"`
	ModifiedSize           uint64 `json:"modified_size"`
	UTF8Count              uint64 `json:"utf8_count,omitempty"`
	UTF8Size               uint64 `json:"utf8_size,omitempty"`
	UTF8SizeHuman          string `json:"utf8_size_human,omitempty"`
	BinaryCount            uint64 `json:"binary_count,omitempty"`
	BinarySize             uint64 `json:"binary_size,omitempty"`
	BinarySizeHuman        string `json:"binary_size_human,omitempty"`
	SetuidCount            uint64 `json:"setuid_count,omitempty"`
	SetgidCount            uint64 `json:"setgid_count,omitempty"`
	StickyCount            uint64 `json:"sticky_count,omitempty"`
}

type ManifestObject

type ManifestObject struct {
	Config   string   //"IMAGE_ID.json" (no sha256 prefix)
	RepoTags []string `json:",omitempty"` //["user/repo:tag"]
	Layers   []string //"LAYER_ID/layer.tar"
}

func LoadManifestObject

func LoadManifestObject(archivePath, imageID string) (*ManifestObject, error)

type ObjectHistory

type ObjectHistory struct {
	Add      *ChangeInfo   `json:"A,omitempty"`
	Modifies []*ChangeInfo `json:"M,omitempty"`
	Delete   *ChangeInfo   `json:"D,omitempty"`
}

type ObjectMetadata

type ObjectMetadata struct {
	Change           ChangeType     `json:"change"`
	DirContentDelete bool           `json:"dir_content_delete,omitempty"`
	Name             string         `json:"name"`
	Size             int64          `json:"size,omitempty"`
	SizeHuman        string         `json:"size_human,omitempty"` //not used yet
	Mode             os.FileMode    `json:"mode,omitempty"`
	ModeHuman        string         `json:"mode_human,omitempty"`
	UID              int            `json:"uid"` //don't omit uid 0
	GID              int            `json:"gid"` //don't omit gid 0
	ModTime          time.Time      `json:"mod_time,omitempty"`
	ChangeTime       time.Time      `json:"change_time,omitempty"`
	LinkTarget       string         `json:"link_target,omitempty"`
	History          *ObjectHistory `json:"history,omitempty"`
	Hash             string         `json:"hash,omitempty"`
	PathMatch        bool           `json:"-"`
	LayerIndex       int            `json:"-"`
	TypeFlag         byte           `json:"-"`
	ContentType      string         `json:"content_type,omitempty"`
}

type Package

type Package struct {
	Manifest        *ManifestObject
	Config          *ConfigObject
	Layers          []*Layer
	LayerIDRefs     map[string]*Layer
	HashReferences  map[string]map[string]*ObjectMetadata
	Stats           PackageStats
	OSShells        map[string]*system.OSShell
	SpecialPermRefs SpecialPermsRefsInfo
	Certs           CertsRefInfo
	CACerts         CertsRefInfo
	IdentityData    *sysidentity.DataSet
}

func LoadPackage

func LoadPackage(archivePath string,
	imageID string,
	skipObjects bool,
	topChangesMax int,
	doHashData bool,
	doDetectDuplicates bool,
	changeDataHashMatchers map[string]*ChangeDataHashMatcher,
	changePathMatchers []*ChangePathMatcher,
	changeDataMatchers map[string]*ChangeDataMatcher,
	utf8Detector *UTF8Detector,
	processorParams *ProcessorParams,
) (*Package, error)

func (*Package) ProcessIdentityData

func (ref *Package) ProcessIdentityData() *sysidentity.Report

type PackageFiles

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

func NewPackageFiles

func NewPackageFiles(archivePath string) (*PackageFiles, error)

func (*PackageFiles) ImageDigest

func (ref *PackageFiles) ImageDigest() string

func (*PackageFiles) ImageID

func (ref *PackageFiles) ImageID() string

func (*PackageFiles) LayerCount

func (ref *PackageFiles) LayerCount() int

func (*PackageFiles) ListImageHistory

func (ref *PackageFiles) ListImageHistory() ([]XHistory, error)

func (*PackageFiles) ListLayerFiles

func (ref *PackageFiles) ListLayerFiles(selectors []FileSelector) ([]*LayerFiles, error)

func (*PackageFiles) ListLayerMetadata

func (ref *PackageFiles) ListLayerMetadata() ([]*LayerMetadata, error)

type PackageStats

type PackageStats struct {
	DuplicateFileCount      uint64 `json:"duplicate_file_count"`
	DuplicateFileTotalCount uint64 `json:"duplicate_file_total_count"`
	DuplicateFileSize       uint64 `json:"duplicate_file_size"`
	DuplicateFileTotalSize  uint64 `json:"duplicate_file_total_size"`
	DuplicateFileWastedSize uint64 `json:"duplicate_file_wasted_size"`
	DeletedCount            uint64 `json:"deleted_count"`
	DeletedDirContentCount  uint64 `json:"deleted_dir_content_count"`
	DeletedDirCount         uint64 `json:"deleted_dir_count"`
	DeletedFileCount        uint64 `json:"deleted_file_count"`
	DeletedLinkCount        uint64 `json:"deleted_link_count"`
	DeletedFileSize         uint64 `json:"deleted_file_size"`
	UTF8Count               uint64 `json:"utf8_count,omitempty"`
	UTF8Size                uint64 `json:"utf8_size,omitempty"`
	UTF8SizeHuman           string `json:"utf8_size_human,omitempty"`
	BinaryCount             uint64 `json:"binary_count,omitempty"`
	BinarySize              uint64 `json:"binary_size,omitempty"`
	BinarySizeHuman         string `json:"binary_size_human,omitempty"`
	SetuidCount             uint64 `json:"setuid_count,omitempty"`
	SetgidCount             uint64 `json:"setgid_count,omitempty"`
	StickyCount             uint64 `json:"sticky_count,omitempty"`
}

type ProcessorParams

type ProcessorParams struct {
	DetectIdentities     *DetectOpParam
	DetectScheduledTasks *DetectOpParam
	DetectServices       *DetectOpParam
	DetectSystemHooks    *DetectOpParam

	DetectAllCertFiles   bool
	DetectAllCertPKFiles bool
}

todo: add other processor params (passed separately for now)

type RootFS

type RootFS struct {
	Type    string   `json:"type"`
	DiffIDs []string `json:"diff_ids,omitempty"`
}

type SourceType

type SourceType string
const (
	SourceTypeDockerImage SourceType = "docker-image"
	SourceTypeGit         SourceType = "git"
	SourceTypeHTTP        SourceType = "http"
)

type SpecialPermsInfo

type SpecialPermsInfo struct {
	Setuid []string `json:"setuid,omitempty"`
	Setgid []string `json:"setgid,omitempty"`
	Sticky []string `json:"sticky,omitempty"`
}

type SpecialPermsRefsInfo

type SpecialPermsRefsInfo struct {
	Setuid map[string]*ObjectMetadata
	Setgid map[string]*ObjectMetadata
	Sticky map[string]*ObjectMetadata
}

type TarReadCloser

type TarReadCloser struct {
	io.Reader
	io.Closer
}

type TarWriter

type TarWriter struct {
	Writer *tar.Writer
	// contains filtered or unexported fields
}

func NewTarWriter

func NewTarWriter(name string) (*TarWriter, error)

func (*TarWriter) Close

func (w *TarWriter) Close() error

type TopObjects

type TopObjects []*ObjectMetadata

func NewTopObjects

func NewTopObjects(n int) TopObjects

func (TopObjects) Len

func (to TopObjects) Len() int

func (TopObjects) Less

func (to TopObjects) Less(i, j int) bool

func (TopObjects) List

func (to TopObjects) List() []*ObjectMetadata

func (*TopObjects) Pop

func (to *TopObjects) Pop() interface{}

func (*TopObjects) Push

func (to *TopObjects) Push(x interface{})

func (TopObjects) Swap

func (to TopObjects) Swap(i, j int)

type UTF8Detector

type UTF8Detector struct {
	Dump         bool
	DumpConsole  bool
	DumpDir      string
	DumpArchive  string
	MaxSizeBytes int
	Archive      *TarWriter
	Filters      []UTF8DetectorMatcher
}

func (*UTF8Detector) Close

func (d *UTF8Detector) Close() error

type UTF8DetectorMatcher

type UTF8DetectorMatcher struct {
	PathPattern string
	DataPattern string
	Matcher     *regexp.Regexp
}

type V1ConfigObject

type V1ConfigObject struct {
	// ID is a unique 64 character identifier of the image
	ID string `json:"id,omitempty"`
	// Parent is the ID of the parent image
	Parent string `json:"parent,omitempty"`
	// Comment is the commit message that was set when committing the image
	Comment string `json:"comment,omitempty"`
	// Created is the timestamp at which the image was created
	Created time.Time `json:"created"`
	// Container is the id of the container used to commit
	Container string `json:"container,omitempty"`
	// ContainerConfig is the configuration of the container that is committed into the image
	ContainerConfig ContainerConfig `json:"container_config,omitempty"`
	// DockerVersion specifies the version of Docker that was used to build the image
	DockerVersion string `json:"docker_version,omitempty"`
	// Author is the name of the author that was specified when committing the image
	Author string `json:"author,omitempty"`
	// Config is the configuration of the container received from the client
	Config *ContainerConfig `json:"config,omitempty"`
	// Architecture is the hardware that the image is built and runs on
	Architecture string `json:"architecture,omitempty"`
	// Variant is the CPU architecture variant (presently ARM-only)
	Variant string `json:"variant,omitempty"`
	// OS is the operating system used to build and run the image
	OS string `json:"os,omitempty"`
	// Size is the total size of the image including all layers it is composed of
	Size int64 `json:",omitempty"`
}

type XHistory

type XHistory struct {
	// Created is the timestamp at which the image was created
	Created time.Time `json:"created"`
	// Author is the name of the author that was specified when committing the image
	Author string `json:"author,omitempty"`
	// CreatedBy keeps the Dockerfile command used while building the image
	CreatedBy string `json:"created_by,omitempty"`
	// Comment is the commit message that was set when committing the image
	Comment string `json:"comment,omitempty"`
	// EmptyLayer is set to true if this history item did not generate a
	// layer. Otherwise, the history item is associated with the next
	// layer in the RootFS section.
	EmptyLayer bool `json:"empty_layer,omitempty"`

	LayerID       string `json:"layer_id,omitempty"`
	LayerIndex    int    `json:"layer_index"`
	LayerFSDiffID string `json:"layer_fsdiff_id,omitempty"`
}

XHistory augments the standard History struct with extra layer info

Jump to

Keyboard shortcuts

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