v1beta1

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

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

Go to latest
Published: Aug 29, 2022 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package v1beta1 contains API Schema definitions for the kmm v1beta1 API group +kubebuilder:object:generate=true +groupName=kmm.sigs.k8s.io

Index

Constants

View Source
const (
	VerificationTrue       string = "True"
	VerificationFalse      string = "False"
	VerificationStageImage string = "Image"
	VerificationStageBuild string = "Build"
)

Variables

View Source
var (
	// GroupVersion is group version used to register these objects
	GroupVersion = schema.GroupVersion{Group: "kmm.sigs.k8s.io", Version: "v1beta1"}

	// SchemeBuilder is used to add go types to the GroupVersionKind scheme
	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

	// AddToScheme adds the types in this group-version to the given scheme.
	AddToScheme = SchemeBuilder.AddToScheme
)

Functions

This section is empty.

Types

type Build

type Build struct {
	// +optional
	// BuildArgs is an array of build variables that are provided to the image building backend.
	BuildArgs []BuildArg `json:"buildArgs"`

	Dockerfile string `json:"dockerfile"`

	// +optional
	// Pull contains settings determining how to check if the DriverContainer image already exists.
	Pull PullOptions `json:"pull"`

	// +optional
	// Push contains settings determining how to push a built DriverContainer image.
	Push PushOptions `json:"push"`

	// +optional
	// Secrets is an optional list of secrets to be made available to the build system.
	// Those secrets should be used for private resources such as a private Github repo.
	// For container registries auth use module.spec.imagePullSecret instead.
	Secrets []v1.LocalObjectReference `json:"secrets"`
}

func (*Build) DeepCopy

func (in *Build) DeepCopy() *Build

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Build.

func (*Build) DeepCopyInto

func (in *Build) DeepCopyInto(out *Build)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type BuildArg

type BuildArg struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

BuildArg represents a build argument used when building a container image.

func (*BuildArg) DeepCopy

func (in *BuildArg) DeepCopy() *BuildArg

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BuildArg.

func (*BuildArg) DeepCopyInto

func (in *BuildArg) DeepCopyInto(out *BuildArg)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type CRStatus

type CRStatus struct {
	// Status of Module CR verification: true (verified), false (verification failed),
	// error (error during verification process), unknown (verification has not started yet)
	// +required
	// +kubebuilder:validation:Required
	// +kubebuilder:validation:Enum=True;False
	VerificationStatus string `json:"verificationStatus"`

	// StatusReason contains a string describing the status source.
	// +optional
	StatusReason string `json:"statusReason,omitempty"`

	// Current stage of the verification process:
	// image (image existence verification), build(build process verification)
	// +required
	// +kubebuilder:validation:Required
	// +kubebuilder:validation:Enum=Image;Build
	VerificationStage string `json:"verificationStage"`

	// LastTransitionTime is the last time the CR status transitioned from one status to another.
	// This should be when the underlying status changed.  If that is not known, then using the time when the API field changed is acceptable.
	// +required
	// +kubebuilder:validation:Required
	// +kubebuilder:validation:Type=string
	// +kubebuilder:validation:Format=date-time
	LastTransitionTime metav1.Time `json:"lastTransitionTime" protobuf:"bytes,4,opt,name=lastTransitionTime"`
}

func (*CRStatus) DeepCopy

func (in *CRStatus) DeepCopy() *CRStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CRStatus.

func (*CRStatus) DeepCopyInto

func (in *CRStatus) DeepCopyInto(out *CRStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type DaemonSetStatus

type DaemonSetStatus struct {
	// number of nodes that are targeted by the module selector
	NodesMatchingSelectorNumber int32 `json:"nodesMatchingSelectorNumber"`
	// number of the pods that should be deployed for daemonset
	DesiredNumber int32 `json:"desiredNumber"`
	// number of the actually deployed and running pods
	AvailableNumber int32 `json:"availableNumber"`
}

DaemonSetStatus contains the status for a daemonset deployed during reconciliation loop

func (*DaemonSetStatus) DeepCopy

func (in *DaemonSetStatus) DeepCopy() *DaemonSetStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DaemonSetStatus.

func (*DaemonSetStatus) DeepCopyInto

func (in *DaemonSetStatus) DeepCopyInto(out *DaemonSetStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type DevicePluginContainerSpec

type DevicePluginContainerSpec struct {
	// Entrypoint array. Not executed within a shell.
	// The container image's ENTRYPOINT is used if this is not provided.
	// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
	// cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced
	// to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will
	// produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless
	// of whether the variable exists or not. Cannot be updated.
	// More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
	// +optional
	Command []string `json:"command,omitempty" protobuf:"bytes,3,rep,name=command"`

	// Arguments to the entrypoint.
	// The container image's CMD is used if this is not provided.
	// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
	// cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced
	// to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will
	// produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless
	// of whether the variable exists or not. Cannot be updated.
	// More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
	// +optional
	Args []string `json:"args,omitempty" protobuf:"bytes,4,rep,name=args"`

	// List of environment variables to set in the container.
	// Cannot be updated.
	// +optional
	// +patchMergeKey=name
	// +patchStrategy=merge
	Env []v1.EnvVar `json:"env,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,7,rep,name=env"`

	// Image is the name of the container image that the device plugin container will run.
	Image string `json:"image"`

	// Image pull policy.
	// One of Always, Never, IfNotPresent.
	// Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.
	// Cannot be updated.
	// More info: https://kubernetes.io/docs/concepts/containers/images#updating-images
	// +optional
	ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy,omitempty" protobuf:"bytes,14,opt,name=imagePullPolicy,casttype=PullPolicy"`

	// Compute Resources required by this container.
	// Cannot be updated.
	// More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
	// +optional
	Resources v1.ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,8,opt,name=resources"`

	// VolumeMounts is a list of volume mounts that are appended to the default ones.
	// +optional
	VolumeMounts []v1.VolumeMount `json:"volumeMounts,omitempty"`
}

func (*DevicePluginContainerSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevicePluginContainerSpec.

func (*DevicePluginContainerSpec) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type DevicePluginSpec

type DevicePluginSpec struct {
	Container DevicePluginContainerSpec `json:"container"`

	// +optional
	// ServiceAccountName is the name of the ServiceAccount to use to run this pod.
	// More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
	ServiceAccountName string `json:"serviceAccountName,omitempty"`

	Volumes []v1.Volume `json:"volumes,omitempty"`
}

func (*DevicePluginSpec) DeepCopy

func (in *DevicePluginSpec) DeepCopy() *DevicePluginSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DevicePluginSpec.

func (*DevicePluginSpec) DeepCopyInto

func (in *DevicePluginSpec) DeepCopyInto(out *DevicePluginSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type KernelMapping

type KernelMapping struct {

	// +optional
	// Build enables in-cluster builds for this mapping and allows overriding the Module's build settings.
	Build *Build `json:"build"`

	// ContainerImage is the name of the DriverContainer image that should be used to deploy the module.
	ContainerImage string `json:"containerImage"`

	// +optional
	// Literal defines a literal target kernel version to be matched exactly against node kernels.
	Literal string `json:"literal"`

	// +optional
	// Regexp is a regular expression to be match against node kernels.
	Regexp string `json:"regexp"`
}

KernelMapping pairs kernel versions with a DriverContainer image. Kernel versions can be matched literally or using a regular expression.

func (*KernelMapping) DeepCopy

func (in *KernelMapping) DeepCopy() *KernelMapping

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KernelMapping.

func (*KernelMapping) DeepCopyInto

func (in *KernelMapping) DeepCopyInto(out *KernelMapping)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ModprobeArgs

type ModprobeArgs struct {
	// Load is an optional list of arguments to be used when loading the kernel module.
	// +kubebuilder:validation:MinItems=1
	Load []string `json:"load,omitempty"`

	// Unload is an optional list of arguments to be used when unloading the kernel module.
	// +kubebuilder:validation:MinItems=1
	Unload []string `json:"unload,omitempty"`
}

func (*ModprobeArgs) DeepCopy

func (in *ModprobeArgs) DeepCopy() *ModprobeArgs

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModprobeArgs.

func (*ModprobeArgs) DeepCopyInto

func (in *ModprobeArgs) DeepCopyInto(out *ModprobeArgs)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ModprobeSpec

type ModprobeSpec struct {
	// ModuleName is the name of the Module to be loaded.
	ModuleName string `json:"moduleName"`

	// Parameters is an optional list of kernel module parameters to be provided to modprobe.
	// They should be in the form of key=value and will be separated by spaces in the modprobe command.
	// The resulting loading command will be: `modprobe module_name ${Parameters}`.
	Parameters []string `json:"parameters,omitempty"`

	// DirName is the root directory for modules.
	// It adds `-d ${DirName}` to the modprobe command-line.
	// +kubebuilder:default=/opt
	DirName string `json:"dirName,omitempty"`

	// Args is an optional list of arguments to be passed to modprobe before the name of the kernel module.
	// The resulting commands will be: `modprobe ${Args} module_name`.
	// +optional
	Args *ModprobeArgs `json:"args,omitempty"`

	// If RawArgs are specified, they are passed straight to the modprobe binary; all other properties in this
	// object are ignored.
	// The resulting commands will be: `modprobe ${RawArgs}`.
	// +optional
	RawArgs *ModprobeArgs `json:"rawArgs,omitempty"`
}

func (*ModprobeSpec) DeepCopy

func (in *ModprobeSpec) DeepCopy() *ModprobeSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModprobeSpec.

func (*ModprobeSpec) DeepCopyInto

func (in *ModprobeSpec) DeepCopyInto(out *ModprobeSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type Module

type Module struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   ModuleSpec   `json:"spec,omitempty"`
	Status ModuleStatus `json:"status,omitempty"`
}

Module is the Schema for the modules API

func (*Module) DeepCopy

func (in *Module) DeepCopy() *Module

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Module.

func (*Module) DeepCopyInto

func (in *Module) DeepCopyInto(out *Module)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*Module) DeepCopyObject

func (in *Module) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ModuleList

type ModuleList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []Module `json:"items"`
}

ModuleList contains a list of Module

func (*ModuleList) DeepCopy

func (in *ModuleList) DeepCopy() *ModuleList

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModuleList.

func (*ModuleList) DeepCopyInto

func (in *ModuleList) DeepCopyInto(out *ModuleList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*ModuleList) DeepCopyObject

func (in *ModuleList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type ModuleLoaderContainerSpec

type ModuleLoaderContainerSpec struct {
	// Build contains build instructions.
	// +optional
	Build *Build `json:"build,omitempty"`

	// ContainerImage is a top-level field
	// +optional
	ContainerImage string `json:"containerImage,omitempty"`

	// Image pull policy.
	// One of Always, Never, IfNotPresent.
	// Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.
	// Cannot be updated.
	// More info: https://kubernetes.io/docs/concepts/containers/images#updating-images
	// +optional
	ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy,omitempty" protobuf:"bytes,14,opt,name=imagePullPolicy,casttype=PullPolicy"`

	// KernelMappings is a list of kernel mappings.
	// When a node's labels match Selector, then the KMM Operator will look for the first mapping that matches its
	// kernel version, and use the corresponding container image to run the DriverContainer.
	// +kubebuilder:validation:MinItems=1
	KernelMappings []KernelMapping `json:"kernelMappings"`

	// Modprobe is a set of properties to customize which module modprobe loads and with which properties.
	Modprobe ModprobeSpec `json:"modprobe"`
}

func (*ModuleLoaderContainerSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModuleLoaderContainerSpec.

func (*ModuleLoaderContainerSpec) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ModuleLoaderSpec

type ModuleLoaderSpec struct {
	// Container holds the properties for the module loader container that runs modprobe.
	Container ModuleLoaderContainerSpec `json:"container"`

	// +optional
	// ServiceAccountName is the name of the ServiceAccount to use to run this pod.
	// More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
	ServiceAccountName string `json:"serviceAccountName,omitempty"`
}

func (*ModuleLoaderSpec) DeepCopy

func (in *ModuleLoaderSpec) DeepCopy() *ModuleLoaderSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModuleLoaderSpec.

func (*ModuleLoaderSpec) DeepCopyInto

func (in *ModuleLoaderSpec) DeepCopyInto(out *ModuleLoaderSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ModuleSpec

type ModuleSpec struct {
	// DevicePlugin allows overriding some properties of the container that deploys the device plugin on the node.
	// Name is ignored and is set automatically by the KMM Operator.
	// +optional
	DevicePlugin *DevicePluginSpec `json:"devicePlugin"`

	// ModuleLoader allows overriding some properties of the container that loads the kernel module on the node.
	// Name and image are ignored and are set automatically by the KMM Operator.
	ModuleLoader ModuleLoaderSpec `json:"moduleLoader"`

	// ImageRepoSecret is an optional secret that is used to pull both the module loader and the device plugin, and
	// to push the resulting image from the module loader build, if enabled.
	// +optional
	ImageRepoSecret *v1.LocalObjectReference `json:"imageRepoSecret,omitempty"`

	// Selector describes on which nodes the Module should be loaded and optionally built.
	Selector map[string]string `json:"selector"`
}

ModuleSpec describes how the KMM operator should deploy a Module on those nodes that need it.

func (*ModuleSpec) DeepCopy

func (in *ModuleSpec) DeepCopy() *ModuleSpec

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModuleSpec.

func (*ModuleSpec) DeepCopyInto

func (in *ModuleSpec) DeepCopyInto(out *ModuleSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type ModuleStatus

type ModuleStatus struct {
	// DevicePlugin contains the status of the Device Plugin daemonset
	// if it was deployed during reconciliation
	DevicePlugin DaemonSetStatus `json:"devicePlugin,omitempty"`
	// ModuleLoader contains the status of the ModuleLoader daemonset
	ModuleLoader DaemonSetStatus `json:"moduleLoader"`
}

ModuleStatus defines the observed state of Module.

func (*ModuleStatus) DeepCopy

func (in *ModuleStatus) DeepCopy() *ModuleStatus

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModuleStatus.

func (*ModuleStatus) DeepCopyInto

func (in *ModuleStatus) DeepCopyInto(out *ModuleStatus)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PreflightValidation

type PreflightValidation struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   PreflightValidationSpec   `json:"spec,omitempty"`
	Status PreflightValidationStatus `json:"status,omitempty"`
}

PreflightValidation initiates a preflight validations for all SpecialResources on the current Kuberentes cluster. +kubebuilder:resource:path=preflightvalidations,scope=Cluster +kubebuilder:resource:path=preflightvalidations,scope=Cluster,shortName=pv

func (*PreflightValidation) DeepCopy

func (in *PreflightValidation) DeepCopy() *PreflightValidation

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightValidation.

func (*PreflightValidation) DeepCopyInto

func (in *PreflightValidation) DeepCopyInto(out *PreflightValidation)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*PreflightValidation) DeepCopyObject

func (in *PreflightValidation) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type PreflightValidationList

type PreflightValidationList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`

	// List of PreflightValidation. More info:
	// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md
	Items []PreflightValidation `json:"items"`
}

PreflightValidationList is a list of PreflightValidation objects.

func (*PreflightValidationList) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightValidationList.

func (*PreflightValidationList) DeepCopyInto

func (in *PreflightValidationList) DeepCopyInto(out *PreflightValidationList)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

func (*PreflightValidationList) DeepCopyObject

func (in *PreflightValidationList) DeepCopyObject() runtime.Object

DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.

type PreflightValidationSpec

type PreflightValidationSpec struct {
	// KernelImage describes the kernel image that all Modules need to be checked against.
	// +kubebuilder:validation:Required
	KernelVersion string `json:"kernelVersion"`
}

PreflightValidationSpec describes the desired state of the resource, such as the kernel version that Module CRs need to be verified against as well as the debug configuration of the logs More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status +kubebuilder:validation:Required

func (*PreflightValidationSpec) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightValidationSpec.

func (*PreflightValidationSpec) DeepCopyInto

func (in *PreflightValidationSpec) DeepCopyInto(out *PreflightValidationSpec)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PreflightValidationStatus

type PreflightValidationStatus struct {
	// CRStatuses contain observations about each SpecialResource's preflight upgradability validation
	// +patchMergeKey=type
	// +patchStrategy=merge
	// +optional
	CRStatuses map[string]*CRStatus `json:"crStatuses,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

PreflightValidationStatus is the most recently observed status of the PreflightValidation. It is populated by the system and is read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

func (*PreflightValidationStatus) DeepCopy

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PreflightValidationStatus.

func (*PreflightValidationStatus) DeepCopyInto

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PullOptions

type PullOptions struct {

	// +optional
	// If Insecure is true, images can be pulled from an insecure (plain HTTP) registry.
	Insecure bool `json:"insecure,omitempty"`

	// +optional
	// If InsecureSkipTLSVerify, the operator will accept any certificate provided by the registry.
	InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
}

func (*PullOptions) DeepCopy

func (in *PullOptions) DeepCopy() *PullOptions

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PullOptions.

func (*PullOptions) DeepCopyInto

func (in *PullOptions) DeepCopyInto(out *PullOptions)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

type PushOptions

type PushOptions struct {

	// +optional
	// If Insecure is true, built images can be pushed to an insecure (plain HTTP) registry.
	Insecure bool `json:"insecure,omitempty"`

	// +optional
	// If InsecureSkipTLSVerify, the operator will accept any certificate provided by the registry.
	InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
}

func (*PushOptions) DeepCopy

func (in *PushOptions) DeepCopy() *PushOptions

DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushOptions.

func (*PushOptions) DeepCopyInto

func (in *PushOptions) DeepCopyInto(out *PushOptions)

DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.

Jump to

Keyboard shortcuts

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