component

package
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: May 31, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package component provides the core framework for managing Kubernetes resources as logical components.

Index

Constants

View Source
const (
	// Unknown indicates that the component has not been reconciled yet.
	Unknown Status = "Unknown"
	// Error indicates that resource errors happened during reconciliation.
	Error Status = "Error"

	// Healthy indicates that the component is fully provisioned and operating as expected.
	Healthy = Status(concepts.AliveConvergingStatusHealthy)
	// AliveCreating indicates that the resource are being created.
	AliveCreating = Status(concepts.AliveConvergingStatusCreating)
	// AliveScaling indicates that the resources are being scaled.
	AliveScaling = Status(concepts.AliveConvergingStatusScaling)
	// AliveUpdating indicates that the resources are being updated.
	AliveUpdating = Status(concepts.AliveConvergingStatusUpdating)
	// AliveFailing indicates that the resources are failing to converge.
	AliveFailing = Status(concepts.AliveConvergingStatusFailing)

	// Operational indicates that the resources are operational.
	Operational = Status(concepts.OperationalStatusOperational)
	// OperationPending indicates that operational resources are pending and not yet operational.
	OperationPending = Status(concepts.OperationalStatusPending)
	// OperationFailing indicates that operational resources are failing to become operational.
	OperationFailing = Status(concepts.OperationalStatusFailing)

	// Completed indicates that completable resources successfully completed their task.
	Completed = Status(concepts.CompletionStatusCompleted)
	// CompletionPending indicates that completable resources are still pending.
	CompletionPending = Status(concepts.CompletionStatusPending)
	// CompletionRunning indicates that completable tasks are still running.
	CompletionRunning = Status(concepts.CompletionStatusRunning)
	// CompletionFailing indicates that completable tasks are failing.
	CompletionFailing = Status(concepts.CompletionStatusFailing)

	// GuardBlocked indicates that a resource's guard precondition is not yet met.
	// The resource and all resources after it in registration order are waiting.
	GuardBlocked = Status(concepts.GuardStatusBlocked)

	// PrerequisiteNotMet indicates that a component-level prerequisite has not been satisfied.
	// The component has not reconciled any resources and is waiting for the prerequisite to be met.
	// Unlike resource guards, prerequisites are initialization barriers: once the component
	// passes through to normal reconciliation for the first time, the prerequisite is never
	// re-evaluated.
	PrerequisiteNotMet Status = "PrerequisiteNotMet"

	// Disabled indicates that the component's feature gate is disabled.
	// All resources managed by the component are deleted when the gate is disabled.
	// The condition status is True because the component is in its expected state.
	Disabled Status = "Disabled"
	// FeatureGateError indicates that the component's feature gate check failed
	// with an error. This is distinct from a generic Error so that the prerequisite
	// initialization barrier can distinguish pre-prerequisite failures from
	// post-prerequisite failures.
	FeatureGateError Status = "FeatureGateError"

	// PendingSuspension indicates that the component is aware of the suspension request but has yet to begin suspension.
	PendingSuspension = Status(concepts.SuspensionStatusPending)
	// Suspending indicates that the component is converging towards a suspended state but is not yet fully suspended.
	Suspending = Status(concepts.SuspensionStatusSuspending)
	// Suspended indicates that the component is suspended.
	Suspended = Status(concepts.SuspensionStatusSuspended)

	// Degraded indicates that the component is degraded but operational.
	Degraded = Status(concepts.GraceStatusDegraded)
	// Down indicates that component is down and not operational.
	Down = Status(concepts.GraceStatusDown)
)

Variables

This section is empty.

Functions

func FlushStatus added in v0.8.0

func FlushStatus(ctx context.Context, rec ReconcileContext) error

FlushStatus persists the owner's current status conditions to the Kubernetes API and records condition metrics for every condition on the owner.

Controllers must call FlushStatus exactly once per reconciliation, typically via defer so that conditions set on error paths are still persisted:

func (r *MyReconciler) Reconcile(ctx context.Context, req reconcile.Request) (res reconcile.Result, err error) {
    owner := &v1alpha1.MyApp{}
    if err := r.Get(ctx, req.NamespacedName, owner); err != nil {
        return reconcile.Result{}, client.IgnoreNotFound(err)
    }
    rec := component.ReconcileContext{ /* ... */ Owner: owner}
    defer func() {
        if flushErr := component.FlushStatus(ctx, rec); flushErr != nil && err == nil {
            err = flushErr
        }
    }()
    return reconcile.Result{}, comp.Reconcile(ctx, rec)
}

On a 409 Conflict (for example if an external writer updated the owner between the controller fetching it and this call) FlushStatus refetches the owner, re-applies the conditions staged during reconciliation using meta.SetStatusCondition, and retries. Conditions managed by other writers on the owner are preserved because meta.SetStatusCondition merges by condition type.

rec.Client and rec.Owner must be populated. If rec.Metrics is nil, metric recording is skipped.

Types

type Builder

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

Builder implements the fluent API for constructing and validating a Component. It ensures that a component is configured with consistent rules before it is used in a reconciliation loop.

func NewComponentBuilder

func NewComponentBuilder() *Builder

NewComponentBuilder initializes a new Builder for creating a Component.

A Component manages a single condition on an owning object (the OperatorCRD) and aggregates the lifecycle, readiness, and suspension state of all registered resources.

func (*Builder) Build

func (b *Builder) Build() (*Component, error)

Build finalizes the component configuration and validates all settings and resources added.

If any validation errors occurred during the fluent configuration (e.g., duplicate resources or invalid grace periods), Build returns a single aggregated error containing all failures using errors.Join.

Returns:

  • *Component: The fully configured and validated component instance on success.
  • error: An aggregated error containing all validation failures, or nil if successful.

func (*Builder) IncludeWhen added in v0.13.0

func (b *Builder) IncludeWhen(include bool, build func() Resource, opts ...ResourceOption) *Builder

IncludeWhen registers build()'s resource only when include is true. Its primary purpose is optional, externally-owned resources that may or may not exist: most commonly a read-only reference to a Secret or ConfigMap owned by the user or another operator, behind an optional spec field.

When include is false the resource is omitted entirely: build is never called, the resource is never created, read, or deleted, and it takes no part in the duplicate-Identity() check. Because build runs only when include is true, it may safely dereference the optional inputs that determined include.

IncludeWhen never deletes, which is the key distinction from GatedBy and DeleteWhen. Reach for GatedBy (or DeleteWhen) to conditionally render a resource the component owns, which is removed from the cluster when its condition turns off. Reach for IncludeWhen to include an optional resource, or to stop managing a resource without deleting it: passing include=false leaves an already-present resource in place, unmanaged, rather than removing it.

When include is true the resource is registered exactly as WithResource with the same options. A nil build function (when include is true) is recorded as a build error rather than panicking.

func (*Builder) Suspend added in v0.2.0

func (b *Builder) Suspend(suspend bool) *Builder

Suspend allows marking the component as suspended.

By default, the component is not suspended, which equates to passing false to this method. When true is passed, all resource within the component that implement the concepts.Suspendable interface are suspended.

func (*Builder) WithConditionType

func (b *Builder) WithConditionType(conditionType ConditionType) *Builder

WithConditionType sets the Kubernetes condition type associated with this component.

This condition type will be updated on the owning object's status to reflect the aggregate state of all resources managed by this component.

Parameters:

  • conditionType: The condition name (e.g., "WebInterfaceReady").

If the condition type is empty, a validation error is recorded and will be returned by Build().

func (*Builder) WithFeatureGate added in v0.6.0

func (b *Builder) WithFeatureGate(gate feature.Gate) *Builder

WithFeatureGate sets a feature gate that controls whether the component is active.

When the gate is disabled, the component deletes all of its resources and reports a True condition with reason Disabled. When the gate is enabled (or not set), the component reconciles normally.

A disabled gate takes precedence over suspension: if the gate is disabled, the component is removed regardless of the suspension flag.

A nil gate is ignored. Calling WithFeatureGate more than once records a validation error that is returned by Build.

func (*Builder) WithGracePeriod

func (b *Builder) WithGracePeriod(gracePeriod time.Duration) *Builder

WithGracePeriod configures a grace duration for the component's convergence to a Ready state.

When a component is not Ready, it is considered to be in a Progressing state (e.g., Creating, Updating, Scaling). The grace period defines how long the component is allowed to remain in these Progressing states before it is considered Degraded or Down.

Once the grace period expires:

  • If the aggregate resource state is Down or Degraded, the component condition transitions to that state.
  • Resources that implement the Alive interface provide their specific grace status used for this aggregation.

Parameters:

  • gracePeriod: The duration to allow for convergence. Must be non-negative.

func (*Builder) WithName

func (b *Builder) WithName(name string) *Builder

WithName sets the name of the component for logging and status identification.

The name is used as the field name in the aggregation of status conditions and must be unique within the owning reconciler.

Parameters:

  • name: A non-empty string identifying the component.

If the name is empty, a validation error is recorded and will be returned by Build().

func (*Builder) WithPrerequisite added in v0.6.0

func (b *Builder) WithPrerequisite(prereq Prerequisite) *Builder

WithPrerequisite registers an initialization barrier for the component.

Prerequisites are evaluated before any resources are reconciled or suspended. The barrier remains active while the condition reason is Unknown, PrerequisiteNotMet, Disabled, or FeatureGateError. Once the condition reason changes to any other value, all prerequisites are permanently passed and never re-evaluated.

Multiple prerequisites may be registered; all must be met before the component proceeds. They are evaluated in registration order and the first unmet prerequisite short-circuits the check.

A nil prerequisite is ignored.

func (*Builder) WithResource

func (b *Builder) WithResource(resource Resource, opts ...ResourceOption) *Builder

WithResource registers a Kubernetes resource to be managed by this component.

Options configure the resource's lifecycle and its participation in health aggregation; see the ResourceOption constructors (ReadOnly, Delete, DeleteWhen, GatedBy, Auxiliary, BlockOnAbsence, IgnoreIfAbsent, SuppressGraceInconsistencyWarning). With no options the resource is created or updated and is required for the component to become Ready.

A nil resource (a nil interface or a typed-nil pointer) is rejected with a build error rather than panicking. If a resource with the same Identity() is already registered, or if the supplied options fail to resolve (a feature-gate evaluation error or an invalid flag combination), a validation error is recorded and returned by Build.

type Component

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

Component represents a logical grouping of Kubernetes resources that are reconciled together and reported as a single condition on an owning object.

A component is responsible for:

  • Creating, updating, or reading its registered resources
  • Aggregating resource-level readiness into a single converging status
  • Managing optional grace-period behavior for degraded or down states
  • Handling suspension, including mutation-based suspension and optional deletion

Each Component manages exactly one condition type on the owner and is reconciled independently of other components. Resources are registered during construction using WithResource and the configuration is finalized by calling Build().

func (*Component) GetCondition

func (c *Component) GetCondition(owner OperatorCRD) Condition

GetCondition returns the current component condition from the owner object. It returns a synthetic "Unknown" condition if the condition is not yet present on the owner.

Note: Always reconcile before retrieving this condition to ensure it reflects the latest cluster state; otherwise, it may be stale.

func (*Component) GetName

func (c *Component) GetName() string

GetName returns the name of the component, which is used for logging and identification.

func (*Component) Preview added in v0.11.0

func (c *Component) Preview() ([]client.Object, error)

Preview renders the desired state of every managed resource registered on the component, in registration order, without contacting the cluster.

Read-only resources (which are fetched, not applied) and delete resources (removal markers with no desired object) are excluded, leaving the desired shape of the managed resources, suitable for whole-component golden snapshots.

Preview does not evaluate guards. Reconcile stops at the first resource whose guard is Blocked and skips it and all later resources, but a guard's outcome generally depends on cluster state and data extracted from earlier resources, none of which is available in a cluster-free render. Preview therefore returns the full desired set, including resources a given reconcile might skip behind a blocked guard, keeping the snapshot deterministic.

Each managed reconcile resource must implement concepts.Previewable; all built-in primitives do. Preview returns an error if a managed resource does not implement it, renders a nil object, or fails to render.

func (*Component) Reconcile

func (c *Component) Reconcile(ctx context.Context, rec ReconcileContext) error

Reconcile converges the component to the desired state.

A component manages its own condition on the parent and updates it in-memory to represent currently observable facts about the component status. Reconcile never writes to the Kubernetes API status subresource; the controller is responsible for persisting the final status by calling FlushStatus exactly once per reconciliation, typically via defer so that conditions set on error paths are still written.

Reconciliation follows these steps:

  1. Feature gate check: If a feature gate is set and disabled, all resources managed by the component are deleted and the condition is set to True/Disabled. No further processing occurs.

  2. Prerequisite check: If prerequisites are registered and the initialization barrier has not yet been passed, all prerequisites are evaluated. The barrier is considered active while the condition reason is Unknown, PrerequisiteNotMet, Disabled, or FeatureGateError. If any prerequisite is not met, the condition is set to False/PrerequisiteNotMet and no resources are reconciled or suspended. Once the condition reason changes to any other value, the barrier is permanently cleared and prerequisites are never re-evaluated.

  3. Suspension check: If the component is marked as suspended, it performs suspension of all managed (non-read-only) resources. Guards are not evaluated. The status is updated to reflect suspension progress (PendingSuspension, Suspending, or Suspended), and then deletion resources are processed.

  4. Resource reconciliation: All non-delete resources are processed sequentially in registration order, regardless of whether they are managed or read-only. For each resource: - Its guard (if any) is evaluated. A blocked guard stops processing of that resource and all subsequent resources. - The resource is either applied (managed) or fetched (read-only). - Its data extractors run immediately, making extracted data available to subsequent resources' guards and mutations.

  5. Status Aggregation: Collects converging status from all processed resources (including any blocked guard result).

  6. Condition Update: Derives a new component condition using a stateful progression model that considers the aggregate resource status, the previous condition, and the configured grace period to avoid churn. The new condition is written to the owner in memory only; call FlushStatus to persist.

  7. Resource Deletion: Finally, it deletes any resources registered for deletion.

func (*Component) Resource added in v0.11.0

func (c *Component) Resource(identity string) (Resource, bool)

Resource returns the registered resource with the given Identity() and true, or nil and false if no such resource is registered. The lookup covers every registered resource, including read-only and delete resources.

type Condition

type Condition metav1.Condition

Condition is a type alias for metav1.Condition. It represents a single condition for a component.

func (Condition) ComponentStatus

func (c Condition) ComponentStatus() Status

ComponentStatus returns the component's internal status (Reason) from the condition.

func (Condition) ConditionType

func (c Condition) ConditionType() ConditionType

ConditionType returns the component-specific type for the condition.

type ConditionType

type ConditionType string

ConditionType represents the type of condition in the Kubernetes status. It is used to identify the specific component's condition on the owner CRD.

type OperatorCRD

type OperatorCRD interface {
	client.Object

	// GetStatusConditions returns a pointer to the slice of status conditions.
	// This is used to read and update the component's status condition on the owner.
	GetStatusConditions() *[]metav1.Condition
	// GetKind returns the string representation of the CRD's Kind.
	GetKind() string
}

OperatorCRD defines the interface for the custom resource that owns the component. It must support status conditions and provide its kind for recording purposes.

type ParticipationMode added in v0.2.0

type ParticipationMode string

ParticipationMode describes in what way the resource participates in the component health aggregation.

const (
	// ParticipationModeRequired The resource must be in a 'Healthy', 'Completed' or 'Operational' state for the
	// component to be considered healthy.
	//
	//  - concepts.Alive: It must be 'Healthy'
	//  - concepts.Operational: It must be 'Operational'
	//  - concepts.Completable: It must be 'Completed'
	//  - If the resource is static (not implementing any of the concepts mentioned), the mode only
	//    takes effect when the resource has a guard. A guarded static resource can report Blocked,
	//    which participates in health aggregation.
	ParticipationModeRequired ParticipationMode = "Required"
	// ParticipationModeAuxiliary The resource is auxiliary and not part of component health evaluation.
	ParticipationModeAuxiliary ParticipationMode = "Auxiliary"
)

type Prerequisite added in v0.6.0

type Prerequisite interface {
	// Check evaluates whether the prerequisite is currently satisfied.
	// The ReconcileContext provides access to the owner object and Kubernetes
	// client for implementations that need to inspect cluster state.
	Check(rec ReconcileContext) (PrerequisiteResult, error)
}

Prerequisite is an initialization barrier for a component.

Unlike resource-level guards, a prerequisite is evaluated only while the component's condition reason indicates it has not yet proceeded past initialization. The barrier remains active while the condition reason is Unknown, PrerequisiteNotMet, Disabled, or FeatureGateError. Once the reason changes to any other value, the barrier is permanently passed and the prerequisite is never re-evaluated.

If the prerequisite is not met, the component does not reconcile any resources and reports a PrerequisiteNotMet condition.

func DependsOn added in v0.6.0

func DependsOn(conditionType ConditionType) Prerequisite

DependsOn returns a Prerequisite that is met when the named condition on the owner has Status=True. This is the common case for expressing that one component depends on another component being ready.

The check reads the owner's in-memory status conditions from the ReconcileContext, so it does not perform any cluster reads.

type PrerequisiteResult added in v0.6.0

type PrerequisiteResult struct {
	// Status indicates whether the prerequisite is met or not.
	Status PrerequisiteStatus
	// Reason provides a human-readable explanation when the prerequisite is not met.
	Reason string
}

PrerequisiteResult carries the outcome of a prerequisite check along with a human-readable reason when the prerequisite is not met.

type PrerequisiteStatus added in v0.6.0

type PrerequisiteStatus string

PrerequisiteStatus represents whether a component-level prerequisite is satisfied.

const (
	// PrerequisiteStatusMet indicates that the prerequisite is satisfied and the component may proceed.
	PrerequisiteStatusMet PrerequisiteStatus = "Met"
	// PrerequisiteStatusNotMet indicates that the prerequisite is not yet satisfied.
	PrerequisiteStatusNotMet PrerequisiteStatus = "NotMet"
)

type ReconcileContext

type ReconcileContext struct {
	// Client is the Kubernetes client for resource operations.
	Client client.Client
	// Scheme is the runtime scheme for the operator.
	Scheme *runtime.Scheme
	// Recorder is the event recorder for publishing Kubernetes events.
	Recorder record.EventRecorder
	// Metrics is the recorder for status condition metrics. It is optional; if
	// nil, [FlushStatus] will skip metric emission.
	Metrics Recorder
	// Owner is the custom resource that owns and is updated by the components.
	Owner OperatorCRD
}

ReconcileContext carries the dependencies and target object for a reconciliation loop.

type Recorder

type Recorder interface {
	// RecordConditionFor records a condition change for a specific object and kind.
	RecordConditionFor(
		kind string, object ocm.ObjectLike,
		conditionType, conditionStatus, conditionReason string, lastTransitionTime time.Time,
		extraLabelValues ...string,
	)
}

Recorder is an interface for recording status condition changes as metrics. It is optional: a ReconcileContext may leave [ReconcileContext.Metrics] nil, in which case FlushStatus skips metric emission.

type Resource

type Resource interface {
	// Mutate applies all applicable mutations on the resource retrieved from the kubernetes api.
	//
	// If the object exists: `current` is the object fetched from the API server.
	// If it does not exist: `current` is the same base object returned by Object() and passed into CreateOrUpdate,
	// not a fetched server object.
	//
	// The Resource implementation is responsible for applying all desired fields from its
	// internal core state to the current resource before proceeding with feature mutations.
	//
	// The mutations are applied every time the component is reconciled.
	Mutate(current client.Object) error
	// Object returns a copy of the managed Kubernetes resource.
	//
	// The returned object's state depends on the reconciliation phase:
	//   - Before reconciliation: Represents the baseline state before any mutations or side effects.
	//   - After reconciliation: Represents the state as applied to the Kubernetes API, including all mutations.
	//
	// This method is primarily intended for use by the Component reconciler. Implementers should
	// avoid calling this directly to retrieve data; instead, use provided patterns like DataExtractable.
	Object() (client.Object, error)
	// Identity returns a unique identifier for the resource in the format <apiVersion>/<kind>/<name>.
	Identity() string
}

Resource is a generic interface for handling Kubernetes resources within a Component. Implementations of this interface wrap a specific Kubernetes object and define how it participates in the component's lifecycle.

type ResourceOption added in v0.13.0

type ResourceOption func(*resourceConfig)

ResourceOption configures how a single resource is managed within a component.

Options are supplied to Builder.WithResource and Builder.IncludeWhen and are resolved at registration time. Any resolution error (a failed feature-gate evaluation or an invalid combination of flags) is recorded on the component builder and returned by Build.

Options operate on the lifecycle axis: they describe what to do with a resource that is present in the reconcile set. Whether a resource is present at all is the presence axis, controlled by WithResource (always present) and IncludeWhen (present only when a condition holds).

A nil ResourceOption is ignored, so a conditionally-assigned option (var opt ResourceOption; ...) may be passed without a nil check.

func Auxiliary added in v0.13.0

func Auxiliary() ResourceOption

Auxiliary sets the resource's participation mode to Auxiliary, excluding its health from the component's aggregation.

func BlockOnAbsence added in v0.13.0

func BlockOnAbsence() ResourceOption

BlockOnAbsence opts a read-only resource into guard-blocked semantics when the cluster reports NotFound: a blocked status ("waiting for <resource>") is recorded and the remaining resources are short-circuited, instead of erroring back through controller-runtime's exponential backoff. Requires ReadOnly and is mutually exclusive with IgnoreIfAbsent; Build returns an error otherwise.

func Delete added in v0.13.0

func Delete() ResourceOption

Delete marks the resource for unconditional removal from the cluster. It is shorthand for DeleteWhen(true).

func DeleteWhen added in v0.13.0

func DeleteWhen(condition bool) ResourceOption

DeleteWhen marks the resource for removal from the cluster when condition is true. Calls are additive with OR semantics: the resource is deleted if any supplied condition is true (or a GatedBy gate is disabled). Mutually exclusive with ReadOnly, since a read-only resource is never deleted; combining them is a configuration error returned by Build.

func GatedBy added in v0.13.0

func GatedBy(gate feature.Gate) ResourceOption

GatedBy conditionally renders an owned resource based on a feature.Gate: it is the option to reach for when a resource the component owns should exist for some feature states and be removed for others. When the gate is disabled the resource is marked for deletion; when enabled it is managed normally. A nil gate is a no-op (treated as always enabled). A gate whose evaluation fails produces a resolution error returned by Build.

Because a disabled gate deletes the resource, GatedBy is mutually exclusive with ReadOnly; combining them is a configuration error returned by Build. For an optional resource that must never be deleted (a read-only reference owned by someone else), use IncludeWhen instead, which omits rather than deletes.

func IgnoreIfAbsent added in v0.13.0

func IgnoreIfAbsent() ResourceOption

IgnoreIfAbsent opts a read-only resource into optional semantics: a NotFound from the cluster is silently ignored and reconciliation continues. Requires ReadOnly and is mutually exclusive with BlockOnAbsence; Build returns an error otherwise.

func ReadOnly added in v0.13.0

func ReadOnly() ResourceOption

ReadOnly marks the resource as read-only: the component fetches its current state but never creates, updates, or deletes it. A read-only resource is not owned by the component, so it is mutually exclusive with every deletion trigger: combining ReadOnly with Delete, DeleteWhen, or GatedBy is a configuration error returned by Build. To conditionally include a read-only resource, use IncludeWhen, which omits the resource without ever deleting it.

func SuppressGraceInconsistencyWarning added in v0.13.0

func SuppressGraceInconsistencyWarning() ResourceOption

SuppressGraceInconsistencyWarning suppresses the warning log emitted when the resource's grace handler returns Healthy while its convergence handler returns non-healthy. Use this when the inconsistency is intentional.

type Status

type Status string

Status represents the internal state of a component. It is used as the Reason in a Kubernetes condition and provides a standardized way to reflect the health and progress of a component.

func (Status) Healthy added in v0.2.0

func (s Status) Healthy() bool

Healthy returns true if the status signals healthiness.

func (Status) Priority added in v0.2.0

func (s Status) Priority() int

Priority returns the aggregation priority of a component Status.

The returned value is used when multiple component statuses must be collapsed into a single overall status (for example by a parent component or system-level status aggregator). The status with the highest Priority() should be selected as the representative state.

The ordering reflects the most meaningful explanation of the system's current state:

Unknown or unrecognized statuses return 0 and therefore do not influence aggregation.

Directories

Path Synopsis
Package concepts defines the core concepts for the operator component framework.
Package concepts defines the core concepts for the operator component framework.

Jump to

Keyboard shortcuts

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