From 691bd50143aec2d23d1111cf3c8cfe4bbf9e85ae Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Mon, 7 Feb 2022 12:34:43 +0100 Subject: [PATCH] Document HelmRepository API v1beta2 spec Signed-off-by: Hidde Beydals --- api/v1beta2/helmrepository_types.go | 86 ++- ...ce.toolkit.fluxcd.io_helmrepositories.yaml | 41 +- controllers/helmrepository_controller.go | 106 ++-- controllers/helmrepository_controller_test.go | 20 +- docs/api/source.md | 92 +-- docs/spec/v1beta2/README.md | 2 +- docs/spec/v1beta2/helmrepositories.md | 534 ++++++++++++++++++ 7 files changed, 721 insertions(+), 160 deletions(-) create mode 100644 docs/spec/v1beta2/helmrepositories.md diff --git a/api/v1beta2/helmrepository_types.go b/api/v1beta2/helmrepository_types.go index 15ff3fbb..9a7ee56e 100644 --- a/api/v1beta2/helmrepository_types.go +++ b/api/v1beta2/helmrepository_types.go @@ -28,56 +28,62 @@ import ( const ( // HelmRepositoryKind is the string representation of a HelmRepository. HelmRepositoryKind = "HelmRepository" - // HelmRepositoryURLIndexKey is the key to use for indexing HelmRepository - // resources by their HelmRepositorySpec.URL. + // HelmRepositoryURLIndexKey is the key used for indexing HelmRepository + // objects by their HelmRepositorySpec.URL. HelmRepositoryURLIndexKey = ".metadata.helmRepositoryURL" ) -// HelmRepositorySpec defines the reference to a Helm repository. +// HelmRepositorySpec specifies the required configuration to produce an +// Artifact for a Helm repository index YAML. type HelmRepositorySpec struct { - // The Helm repository URL, a valid URL contains at least a protocol and host. + // URL of the Helm repository, a valid URL contains at least a protocol and + // host. // +required URL string `json:"url"` - // The name of the secret containing authentication credentials for the Helm - // repository. - // For HTTP/S basic auth the secret must contain username and - // password fields. - // For TLS the secret must contain a certFile and keyFile, and/or - // caCert fields. + // SecretRef specifies the Secret containing authentication credentials + // for the HelmRepository. + // For HTTP/S basic auth the secret must contain 'username' and 'password' + // fields. + // For TLS the secret must contain a 'certFile' and 'keyFile', and/or + // 'caCert' fields. // +optional SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` - // PassCredentials allows the credentials from the SecretRef to be passed on to - // a host that does not match the host as defined in URL. - // This may be required if the host of the advertised chart URLs in the index - // differ from the defined URL. - // Enabling this should be done with caution, as it can potentially result in - // credentials getting stolen in a MITM-attack. + // PassCredentials allows the credentials from the SecretRef to be passed + // on to a host that does not match the host as defined in URL. + // This may be required if the host of the advertised chart URLs in the + // index differ from the defined URL. + // Enabling this should be done with caution, as it can potentially result + // in credentials getting stolen in a MITM-attack. // +optional PassCredentials bool `json:"passCredentials,omitempty"` - // The interval at which to check the upstream for updates. + // Interval at which to check the URL for updates. // +required Interval metav1.Duration `json:"interval"` - // The timeout of index fetching, defaults to 60s. + // Timeout of the index fetch operation, defaults to 60s. // +kubebuilder:default:="60s" // +optional Timeout *metav1.Duration `json:"timeout,omitempty"` - // This flag tells the controller to suspend the reconciliation of this source. + // Suspend tells the controller to suspend the reconciliation of this + // HelmRepository. // +optional Suspend bool `json:"suspend,omitempty"` - // AccessFrom defines an Access Control List for allowing cross-namespace references to this object. + // AccessFrom specifies an Access Control List for allowing cross-namespace + // references to this object. + // NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092 // +optional AccessFrom *acl.AccessFrom `json:"accessFrom,omitempty"` } -// HelmRepositoryStatus defines the observed state of the HelmRepository. +// HelmRepositoryStatus records the observed state of the HelmRepository. type HelmRepositoryStatus struct { - // ObservedGeneration is the last observed generation. + // ObservedGeneration is the last observed generation of the HelmRepository + // object. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty"` @@ -85,11 +91,13 @@ type HelmRepositoryStatus struct { // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` - // URL is the fetch link for the last index fetched. + // URL is the dynamic fetch link for the latest Artifact. + // It is provided on a "best effort" basis, and using the precise + // HelmRepositoryStatus.Artifact data is recommended. // +optional URL string `json:"url,omitempty"` - // Artifact represents the output of the last successful repository sync. + // Artifact represents the last successful HelmRepository reconciliation. // +optional Artifact *Artifact `json:"artifact,omitempty"` @@ -97,13 +105,9 @@ type HelmRepositoryStatus struct { } const ( - // IndexationFailedReason represents the fact that the indexation of the given - // Helm repository failed. + // IndexationFailedReason signals that the HelmRepository index fetch + // failed. IndexationFailedReason string = "IndexationFailed" - - // IndexationSucceededReason represents the fact that the indexation of the - // given Helm repository succeeded. - IndexationSucceededReason string = "IndexationSucceed" ) // GetConditions returns the status conditions of the object. @@ -116,28 +120,18 @@ func (in *HelmRepository) SetConditions(conditions []metav1.Condition) { in.Status.Conditions = conditions } -// GetRequeueAfter returns the duration after which the source must be reconciled again. +// GetRequeueAfter returns the duration after which the source must be +// reconciled again. func (in HelmRepository) GetRequeueAfter() time.Duration { return in.Spec.Interval.Duration } -// GetInterval returns the interval at which the source is reconciled. -// Deprecated: use GetRequeueAfter instead. -func (in HelmRepository) GetInterval() metav1.Duration { - return in.Spec.Interval -} - -// GetArtifact returns the latest artifact from the source if present in the status sub-resource. +// GetArtifact returns the latest artifact from the source if present in the +// status sub-resource. func (in *HelmRepository) GetArtifact() *Artifact { return in.Status.Artifact } -// GetStatusConditions returns a pointer to the Status.Conditions slice. -// Deprecated: use GetConditions instead. -func (in *HelmRepository) GetStatusConditions() *[]metav1.Condition { - return &in.Status.Conditions -} - // +genclient // +genclient:Namespaced // +kubebuilder:storageversion @@ -149,7 +143,7 @@ func (in *HelmRepository) GetStatusConditions() *[]metav1.Condition { // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="" -// HelmRepository is the Schema for the helmrepositories API +// HelmRepository is the Schema for the helmrepositories API. type HelmRepository struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -159,7 +153,7 @@ type HelmRepository struct { Status HelmRepositoryStatus `json:"status,omitempty"` } -// HelmRepositoryList contains a list of HelmRepository +// HelmRepositoryList contains a list of HelmRepository objects. // +kubebuilder:object:root=true type HelmRepositoryList struct { metav1.TypeMeta `json:",inline"` diff --git a/config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml b/config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml index b277494a..ede03f1c 100644 --- a/config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml +++ b/config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml @@ -253,7 +253,7 @@ spec: name: v1beta2 schema: openAPIV3Schema: - description: HelmRepository is the Schema for the helmrepositories API + description: HelmRepository is the Schema for the helmrepositories API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -268,11 +268,13 @@ spec: metadata: type: object spec: - description: HelmRepositorySpec defines the reference to a Helm repository. + description: HelmRepositorySpec specifies the required configuration to + produce an Artifact for a Helm repository index YAML. properties: accessFrom: - description: AccessFrom defines an Access Control List for allowing - cross-namespace references to this object. + description: 'AccessFrom specifies an Access Control List for allowing + cross-namespace references to this object. NOTE: Not implemented, + provisional as of https://github.com/fluxcd/flux2/pull/2092' properties: namespaceSelectors: description: NamespaceSelectors is the list of namespace selectors @@ -298,7 +300,7 @@ spec: - namespaceSelectors type: object interval: - description: The interval at which to check the upstream for updates. + description: Interval at which to check the URL for updates. type: string passCredentials: description: PassCredentials allows the credentials from the SecretRef @@ -309,10 +311,10 @@ spec: getting stolen in a MITM-attack. type: boolean secretRef: - description: The name of the secret containing authentication credentials - for the Helm repository. For HTTP/S basic auth the secret must contain - username and password fields. For TLS the secret must contain a - certFile and keyFile, and/or caCert fields. + description: SecretRef specifies the Secret containing authentication + credentials for the HelmRepository. For HTTP/S basic auth the secret + must contain 'username' and 'password' fields. For TLS the secret + must contain a 'certFile' and 'keyFile', and/or 'caCert' fields. properties: name: description: Name of the referent. @@ -321,15 +323,15 @@ spec: - name type: object suspend: - description: This flag tells the controller to suspend the reconciliation - of this source. + description: Suspend tells the controller to suspend the reconciliation + of this HelmRepository. type: boolean timeout: default: 60s - description: The timeout of index fetching, defaults to 60s. + description: Timeout of the index fetch operation, defaults to 60s. type: string url: - description: The Helm repository URL, a valid URL contains at least + description: URL of the Helm repository, a valid URL contains at least a protocol and host. type: string required: @@ -339,11 +341,11 @@ spec: status: default: observedGeneration: -1 - description: HelmRepositoryStatus defines the observed state of the HelmRepository. + description: HelmRepositoryStatus records the observed state of the HelmRepository. properties: artifact: - description: Artifact represents the output of the last successful - repository sync. + description: Artifact represents the last successful HelmRepository + reconciliation. properties: checksum: description: Checksum is the SHA256 checksum of the Artifact file. @@ -449,11 +451,14 @@ spec: be detected. type: string observedGeneration: - description: ObservedGeneration is the last observed generation. + description: ObservedGeneration is the last observed generation of + the HelmRepository object. format: int64 type: integer url: - description: URL is the fetch link for the last index fetched. + description: URL is the dynamic fetch link for the latest Artifact. + It is provided on a "best effort" basis, and using the precise HelmRepositoryStatus.Artifact + data is recommended. type: string type: object type: object diff --git a/controllers/helmrepository_controller.go b/controllers/helmrepository_controller.go index 39b28e98..5d96ef59 100644 --- a/controllers/helmrepository_controller.go +++ b/controllers/helmrepository_controller.go @@ -50,9 +50,9 @@ import ( "github.com/fluxcd/source-controller/internal/reconcile/summarize" ) -// helmRepoReadyConditions contains all the conditions information needed -// for HelmRepository Ready status conditions summary calculation. -var helmRepoReadyConditions = summarize.Conditions{ +// helmRepositoryReadyCondition contains the information required to summarize a +// v1beta2.HelmRepository Ready Condition. +var helmRepositoryReadyCondition = summarize.Conditions{ Target: meta.ReadyCondition, Owned: []string{ sourcev1.FetchFailedCondition, @@ -80,7 +80,7 @@ var helmRepoReadyConditions = summarize.Conditions{ // +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmrepositories/finalizers,verbs=get;create;update;patch;delete // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch -// HelmRepositoryReconciler reconciles a HelmRepository object +// HelmRepositoryReconciler reconciles a v1beta2.HelmRepository object. type HelmRepositoryReconciler struct { client.Client kuberecorder.EventRecorder @@ -94,10 +94,11 @@ type HelmRepositoryReconcilerOptions struct { MaxConcurrentReconciles int } -// helmRepoReconcilerFunc is the function type for all the helm repository -// reconciler functions. The reconciler functions are grouped together and -// executed serially to perform the main operation of the reconciler. -type helmRepoReconcilerFunc func(ctx context.Context, obj *sourcev1.HelmRepository, artifact *sourcev1.Artifact, repo *repository.ChartRepository) (sreconcile.Result, error) +// helmRepositoryReconcileFunc is the function type for all the +// v1beta2.HelmRepository (sub)reconcile functions. The type implementations +// are grouped and executed serially to perform the complete reconcile of the +// object. +type helmRepositoryReconcileFunc func(ctx context.Context, obj *sourcev1.HelmRepository, artifact *sourcev1.Artifact, repo *repository.ChartRepository) (sreconcile.Result, error) func (r *HelmRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error { return r.SetupWithManagerAndOptions(mgr, HelmRepositoryReconcilerOptions{}) @@ -144,7 +145,7 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque defer func() { summarizeHelper := summarize.NewHelper(r.EventRecorder, patchHelper) summarizeOpts := []summarize.Option{ - summarize.WithConditions(helmRepoReadyConditions), + summarize.WithConditions(helmRepositoryReadyCondition), summarize.WithReconcileResult(recResult), summarize.WithReconcileError(retErr), summarize.WithIgnoreNotFound(), @@ -152,7 +153,7 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque summarize.RecordContextualError, summarize.RecordReconcileReq, ), - summarize.WithResultBuilder(sreconcile.AlwaysRequeueResultBuilder{RequeueAfter: obj.GetInterval().Duration}), + summarize.WithResultBuilder(sreconcile.AlwaysRequeueResultBuilder{RequeueAfter: obj.GetRequeueAfter()}), } result, retErr = summarizeHelper.SummarizeAndPatch(ctx, obj, summarizeOpts...) @@ -176,7 +177,7 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque } // Reconcile actual object - reconcilers := []helmRepoReconcilerFunc{ + reconcilers := []helmRepositoryReconcileFunc{ r.reconcileStorage, r.reconcileSource, r.reconcileArtifact, @@ -185,12 +186,10 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque return } -// reconcile iterates through the sub-reconcilers and processes the source -// object. The sub-reconcilers are run sequentially. The result and error of -// the sub-reconciliation are collected and returned. For multiple results -// from different sub-reconcilers, the results are combined to return the -// result with the shortest requeue period. -func (r *HelmRepositoryReconciler) reconcile(ctx context.Context, obj *sourcev1.HelmRepository, reconcilers []helmRepoReconcilerFunc) (sreconcile.Result, error) { +// reconcile iterates through the gitRepositoryReconcileFunc tasks for the +// object. It returns early on the first call that returns +// reconcile.ResultRequeue, or produces an error. +func (r *HelmRepositoryReconciler) reconcile(ctx context.Context, obj *sourcev1.HelmRepository, reconcilers []helmRepositoryReconcileFunc) (sreconcile.Result, error) { if obj.Generation != obj.Status.ObservedGeneration { conditions.MarkReconciling(obj, "NewGeneration", "reconciling new object generation (%d)", obj.Generation) } @@ -220,12 +219,18 @@ func (r *HelmRepositoryReconciler) reconcile(ctx context.Context, obj *sourcev1. return res, resErr } -// reconcileStorage ensures the current state of the storage matches the desired and previously observed state. +// reconcileStorage ensures the current state of the storage matches the +// desired and previously observed state. // -// All artifacts for the resource except for the current one are garbage collected from the storage. -// If the artifact in the Status object of the resource disappeared from storage, it is removed from the object. -// If the hostname of the URLs on the object do not match the current storage server hostname, they are updated. -func (r *HelmRepositoryReconciler) reconcileStorage(ctx context.Context, obj *sourcev1.HelmRepository, artifact *sourcev1.Artifact, chartRepo *repository.ChartRepository) (sreconcile.Result, error) { +// All Artifacts for the object except for the current one in the Status are +// garbage collected from the Storage. +// If the Artifact in the Status of the object disappeared from the Storage, +// it is removed from the object. +// If the object does not have an Artifact in its Status, a Reconciling +// condition is added. +// The hostname of any URL in the Status of the object are updated, to ensure +// they match the Storage server hostname of current runtime. +func (r *HelmRepositoryReconciler) reconcileStorage(ctx context.Context, obj *sourcev1.HelmRepository, _ *sourcev1.Artifact, _ *repository.ChartRepository) (sreconcile.Result, error) { // Garbage collect previous advertised artifact(s) from storage _ = r.garbageCollect(ctx, obj) @@ -249,13 +254,14 @@ func (r *HelmRepositoryReconciler) reconcileStorage(ctx context.Context, obj *so return sreconcile.ResultSuccess, nil } -// reconcileSource ensures the upstream Helm repository can be reached and downloaded out using the declared -// configuration, and stores a new artifact in the storage. +// reconcileSource attempts to fetch the Helm repository index using the +// specified configuration on the v1beta2.HelmRepository object. // -// The Helm repository index is downloaded using the defined configuration, and in case of an error during this process -// (including transient errors), it records v1beta1.FetchFailedCondition=True and returns early. -// If the download is successful, the given artifact pointer is set to a new artifact with the available metadata, and -// the index pointer is set to the newly downloaded index. +// When the fetch fails, it records v1beta2.FetchFailedCondition=True and +// returns early. +// If successful and the index is valid, any previous +// v1beta2.FetchFailedCondition is removed, and the repository.ChartRepository +// pointer is set to the newly fetched index. func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, obj *sourcev1.HelmRepository, artifact *sourcev1.Artifact, chartRepo *repository.ChartRepository) (sreconcile.Result, error) { // Configure Helm client to access repository clientOpts := []helmgetter.Option{ @@ -289,7 +295,11 @@ func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, obj *sou Reason: sourcev1.StorageOperationFailedReason, } } - defer os.RemoveAll(tmpDir) + defer func() { + if err = os.RemoveAll(tmpDir); err != nil { + ctrl.LoggerFrom(ctx).Error(err, "failed to remove temporary directory for credentials") + } + }() // Construct actual options opts, err := getter.ClientOptionsFromSecret(tmpDir, secret) @@ -366,14 +376,15 @@ func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, obj *sou return sreconcile.ResultSuccess, nil } -// reconcileArtifact stores a new artifact in the storage, if the current observation on the object does not match the -// given data. +// reconcileArtifact archives a new Artifact to the Storage, if the current +// (Status) data on the object does not match the given. // -// The inspection of the given data to the object is differed, ensuring any stale observations as -// v1beta1.ArtifactUnavailableCondition and v1beta1.ArtifactOutdatedCondition are always deleted. -// If the given artifact does not differ from the object's current, it returns early. -// On a successful write of a new artifact, the artifact in the status of the given object is set, and the symlink in -// the storage is updated to its path. +// The inspection of the given data to the object is differed, ensuring any +// stale observations like v1beta2.ArtifactOutdatedCondition are removed. +// If the given Artifact does not differ from the object's current, it returns +// early. +// On a successful archive, the Artifact in the Status of the object is set, +// and the symlink in the Storage is updated to its path. func (r *HelmRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *sourcev1.HelmRepository, artifact *sourcev1.Artifact, chartRepo *repository.ChartRepository) (sreconcile.Result, error) { // Always restore the Ready condition in case it got removed due to a transient error. defer func() { @@ -437,15 +448,16 @@ func (r *HelmRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *s r.eventLogf(ctx, obj, corev1.EventTypeWarning, sourcev1.StorageOperationFailedReason, "failed to update status URL symlink: %s", err) } - if indexURL != "" { obj.Status.URL = indexURL } + return sreconcile.ResultSuccess, nil } -// reconcileDelete handles the delete of an object. It first garbage collects all artifacts for the object from the -// artifact storage, if successful, the finalizer is removed from the object. +// reconcileDelete handles the deletion of the object. +// It first garbage collects all Artifacts for the object from the Storage. +// Removing the finalizer from the object if successful. func (r *HelmRepositoryReconciler) reconcileDelete(ctx context.Context, obj *sourcev1.HelmRepository) (sreconcile.Result, error) { // Garbage collect the resource's artifacts if err := r.garbageCollect(ctx, obj); err != nil { @@ -460,9 +472,11 @@ func (r *HelmRepositoryReconciler) reconcileDelete(ctx context.Context, obj *sou return sreconcile.ResultEmpty, nil } -// garbageCollect performs a garbage collection for the given v1beta1.HelmRepository. It removes all but the current -// artifact except for when the deletion timestamp is set, which will result in the removal of all artifacts for the -// resource. +// garbageCollect performs a garbage collection for the given object. +// +// It removes all but the current Artifact from the Storage, unless the +// deletion timestamp on the object is set. Which will result in the +// removal of all Artifacts for the objects. func (r *HelmRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourcev1.HelmRepository) error { if !obj.DeletionTimestamp.IsZero() { if deleted, err := r.Storage.RemoveAll(r.Storage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), "", "*")); err != nil { @@ -491,9 +505,11 @@ func (r *HelmRepositoryReconciler) garbageCollect(ctx context.Context, obj *sour return nil } -// eventLog records event and logs at the same time. This log is different from -// the debug log in the event recorder in the sense that this is a simple log, -// the event recorder debug log contains complete details about the event. +// eventLogf records event and logs at the same time. +// +// This log is different from the debug log in the EventRecorder, in the sense +// that this is a simple log. While the debug log contains complete details +// about the event. func (r *HelmRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) { msg := fmt.Sprintf(messageFmt, args...) // Log and emit event. diff --git a/controllers/helmrepository_controller_test.go b/controllers/helmrepository_controller_test.go index 9bcf5fd5..da56d488 100644 --- a/controllers/helmrepository_controller_test.go +++ b/controllers/helmrepository_controller_test.go @@ -93,7 +93,7 @@ func TestHelmRepositoryReconciler_Reconcile(t *testing.T) { }, timeout).Should(BeTrue()) // Check if the object status is valid. - condns := &status.Conditions{NegativePolarity: helmRepoReadyConditions.NegativePolarity} + condns := &status.Conditions{NegativePolarity: helmRepositoryReadyCondition.NegativePolarity} checker := status.NewChecker(testEnv.Client, testEnv.GetScheme(), condns) checker.CheckErr(ctx, obj) @@ -626,8 +626,8 @@ func TestHelmRepositoryReconciler_reconcileArtifact(t *testing.T) { } func TestHelmRepositoryReconciler_reconcileSubRecs(t *testing.T) { - // Helper to build simple helmRepoReconcilerFunc with result and error. - buildReconcileFuncs := func(r sreconcile.Result, e error) helmRepoReconcilerFunc { + // Helper to build simple helmRepositoryReconcileFunc with result and error. + buildReconcileFuncs := func(r sreconcile.Result, e error) helmRepositoryReconcileFunc { return func(ctx context.Context, obj *sourcev1.HelmRepository, artifact *sourcev1.Artifact, repo *repository.ChartRepository) (sreconcile.Result, error) { return r, e } @@ -637,14 +637,14 @@ func TestHelmRepositoryReconciler_reconcileSubRecs(t *testing.T) { name string generation int64 observedGeneration int64 - reconcileFuncs []helmRepoReconcilerFunc + reconcileFuncs []helmRepositoryReconcileFunc wantResult sreconcile.Result wantErr bool assertConditions []metav1.Condition }{ { name: "successful reconciliations", - reconcileFuncs: []helmRepoReconcilerFunc{ + reconcileFuncs: []helmRepositoryReconcileFunc{ buildReconcileFuncs(sreconcile.ResultSuccess, nil), }, wantResult: sreconcile.ResultSuccess, @@ -654,7 +654,7 @@ func TestHelmRepositoryReconciler_reconcileSubRecs(t *testing.T) { name: "successful reconciliation with generation difference", generation: 3, observedGeneration: 2, - reconcileFuncs: []helmRepoReconcilerFunc{ + reconcileFuncs: []helmRepositoryReconcileFunc{ buildReconcileFuncs(sreconcile.ResultSuccess, nil), }, wantResult: sreconcile.ResultSuccess, @@ -665,7 +665,7 @@ func TestHelmRepositoryReconciler_reconcileSubRecs(t *testing.T) { }, { name: "failed reconciliation", - reconcileFuncs: []helmRepoReconcilerFunc{ + reconcileFuncs: []helmRepositoryReconcileFunc{ buildReconcileFuncs(sreconcile.ResultEmpty, fmt.Errorf("some error")), }, wantResult: sreconcile.ResultEmpty, @@ -673,7 +673,7 @@ func TestHelmRepositoryReconciler_reconcileSubRecs(t *testing.T) { }, { name: "multiple object status conditions mutations", - reconcileFuncs: []helmRepoReconcilerFunc{ + reconcileFuncs: []helmRepositoryReconcileFunc{ func(ctx context.Context, obj *sourcev1.HelmRepository, artifact *sourcev1.Artifact, repo *repository.ChartRepository) (sreconcile.Result, error) { conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "NewRevision", "new index revision") return sreconcile.ResultSuccess, nil @@ -692,7 +692,7 @@ func TestHelmRepositoryReconciler_reconcileSubRecs(t *testing.T) { }, { name: "subrecs with one result=Requeue, no error", - reconcileFuncs: []helmRepoReconcilerFunc{ + reconcileFuncs: []helmRepositoryReconcileFunc{ buildReconcileFuncs(sreconcile.ResultSuccess, nil), buildReconcileFuncs(sreconcile.ResultRequeue, nil), buildReconcileFuncs(sreconcile.ResultSuccess, nil), @@ -702,7 +702,7 @@ func TestHelmRepositoryReconciler_reconcileSubRecs(t *testing.T) { }, { name: "subrecs with error before result=Requeue", - reconcileFuncs: []helmRepoReconcilerFunc{ + reconcileFuncs: []helmRepositoryReconcileFunc{ buildReconcileFuncs(sreconcile.ResultSuccess, nil), buildReconcileFuncs(sreconcile.ResultEmpty, fmt.Errorf("some error")), buildReconcileFuncs(sreconcile.ResultRequeue, nil), diff --git a/docs/api/source.md b/docs/api/source.md index a1240efa..6f46cff8 100644 --- a/docs/api/source.md +++ b/docs/api/source.md @@ -676,7 +676,7 @@ HelmChartStatus

HelmRepository

-

HelmRepository is the Schema for the helmrepositories API

+

HelmRepository is the Schema for the helmrepositories API.

@@ -739,7 +739,8 @@ string @@ -753,12 +754,12 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference @@ -770,12 +771,12 @@ bool @@ -788,7 +789,7 @@ Kubernetes meta/v1.Duration @@ -802,7 +803,7 @@ Kubernetes meta/v1.Duration @@ -814,7 +815,8 @@ bool @@ -828,7 +830,9 @@ github.com/fluxcd/pkg/apis/acl.AccessFrom
-

The Helm repository URL, a valid URL contains at least a protocol and host.

+

URL of the Helm repository, a valid URL contains at least a protocol and +host.

(Optional) -

The name of the secret containing authentication credentials for the Helm -repository. -For HTTP/S basic auth the secret must contain username and -password fields. -For TLS the secret must contain a certFile and keyFile, and/or -caCert fields.

+

SecretRef specifies the Secret containing authentication credentials +for the HelmRepository. +For HTTP/S basic auth the secret must contain ‘username’ and ‘password’ +fields. +For TLS the secret must contain a ‘certFile’ and ‘keyFile’, and/or +‘caCert’ fields.

(Optional) -

PassCredentials allows the credentials from the SecretRef to be passed on to -a host that does not match the host as defined in URL. -This may be required if the host of the advertised chart URLs in the index -differ from the defined URL. -Enabling this should be done with caution, as it can potentially result in -credentials getting stolen in a MITM-attack.

+

PassCredentials allows the credentials from the SecretRef to be passed +on to a host that does not match the host as defined in URL. +This may be required if the host of the advertised chart URLs in the +index differ from the defined URL. +Enabling this should be done with caution, as it can potentially result +in credentials getting stolen in a MITM-attack.

-

The interval at which to check the upstream for updates.

+

Interval at which to check the URL for updates.

(Optional) -

The timeout of index fetching, defaults to 60s.

+

Timeout of the index fetch operation, defaults to 60s.

(Optional) -

This flag tells the controller to suspend the reconciliation of this source.

+

Suspend tells the controller to suspend the reconciliation of this +HelmRepository.

(Optional) -

AccessFrom defines an Access Control List for allowing cross-namespace references to this object.

+

AccessFrom specifies an Access Control List for allowing cross-namespace +references to this object. +NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092

@@ -1916,7 +1920,8 @@ github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus (Appears on: HelmRepository)

-

HelmRepositorySpec defines the reference to a Helm repository.

+

HelmRepositorySpec specifies the required configuration to produce an +Artifact for a Helm repository index YAML.

@@ -1935,7 +1940,8 @@ string @@ -1949,12 +1955,12 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference @@ -1966,12 +1972,12 @@ bool @@ -1984,7 +1990,7 @@ Kubernetes meta/v1.Duration @@ -1998,7 +2004,7 @@ Kubernetes meta/v1.Duration @@ -2010,7 +2016,8 @@ bool @@ -2024,7 +2031,9 @@ github.com/fluxcd/pkg/apis/acl.AccessFrom @@ -2037,7 +2046,7 @@ github.com/fluxcd/pkg/apis/acl.AccessFrom (Appears on:HelmRepository)

-

HelmRepositoryStatus defines the observed state of the HelmRepository.

+

HelmRepositoryStatus records the observed state of the HelmRepository.

-

The Helm repository URL, a valid URL contains at least a protocol and host.

+

URL of the Helm repository, a valid URL contains at least a protocol and +host.

(Optional) -

The name of the secret containing authentication credentials for the Helm -repository. -For HTTP/S basic auth the secret must contain username and -password fields. -For TLS the secret must contain a certFile and keyFile, and/or -caCert fields.

+

SecretRef specifies the Secret containing authentication credentials +for the HelmRepository. +For HTTP/S basic auth the secret must contain ‘username’ and ‘password’ +fields. +For TLS the secret must contain a ‘certFile’ and ‘keyFile’, and/or +‘caCert’ fields.

(Optional) -

PassCredentials allows the credentials from the SecretRef to be passed on to -a host that does not match the host as defined in URL. -This may be required if the host of the advertised chart URLs in the index -differ from the defined URL. -Enabling this should be done with caution, as it can potentially result in -credentials getting stolen in a MITM-attack.

+

PassCredentials allows the credentials from the SecretRef to be passed +on to a host that does not match the host as defined in URL. +This may be required if the host of the advertised chart URLs in the +index differ from the defined URL. +Enabling this should be done with caution, as it can potentially result +in credentials getting stolen in a MITM-attack.

-

The interval at which to check the upstream for updates.

+

Interval at which to check the URL for updates.

(Optional) -

The timeout of index fetching, defaults to 60s.

+

Timeout of the index fetch operation, defaults to 60s.

(Optional) -

This flag tells the controller to suspend the reconciliation of this source.

+

Suspend tells the controller to suspend the reconciliation of this +HelmRepository.

(Optional) -

AccessFrom defines an Access Control List for allowing cross-namespace references to this object.

+

AccessFrom specifies an Access Control List for allowing cross-namespace +references to this object. +NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092

@@ -2057,7 +2066,8 @@ int64 @@ -2083,7 +2093,9 @@ string @@ -2097,7 +2109,7 @@ Artifact diff --git a/docs/spec/v1beta2/README.md b/docs/spec/v1beta2/README.md index bdb9b1ad..dca99b0e 100644 --- a/docs/spec/v1beta2/README.md +++ b/docs/spec/v1beta2/README.md @@ -6,7 +6,7 @@ This is the v1beta2 API specification for defining the desired state sources of * Source kinds: + GitRepository - + HelmRepository + + [HelmRepository](helmrepositories.md) + HelmChart + [Bucket](buckets.md) diff --git a/docs/spec/v1beta2/helmrepositories.md b/docs/spec/v1beta2/helmrepositories.md new file mode 100644 index 00000000..d92a7ab5 --- /dev/null +++ b/docs/spec/v1beta2/helmrepositories.md @@ -0,0 +1,534 @@ +# Helm Repositories + +The `HelmRepository` API defines a Source to produce an Artifact for a Helm +repository index YAML (`index.yaml`). + +## Example + +The following is an example of a HelmRepository. It creates a YAML (`.yaml`) +Artifact from the fetched Helm repository index (in this example the [podinfo +repository](https://github.com/stefanprodan/podinfo)): + +```yaml +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmRepository +metadata: + name: podinfo + namespace: default +spec: + interval: 5m0s + url: https://stefanprodan.github.io/podinfo +``` + +In the above example: + +- A HelmRepository named `podinfo` is created, indicated by the + `.metadata.name` field. +- The source-controller fetches the Helm repository index YAML every five + minutes from `https://stefanprodan.github.io/podinfo`, indicated by the + `.spec.interval` and `.spec.url` fields. +- The SHA256 sum of the Helm repository index after stable sorting the entries + is used as Artifact revision, reported in-cluster in the + `.status.artifact.revision` field. +- When the current HelmRepository revision differs from the latest fetched + revision, it is stored as a new Artifact. +- The new Artifact is reported in the `.status.artifact` field. + +You can run this example by saving the manifest into `helmrepository.yaml`. + +1. Apply the resource on the cluster: + + ```sh + kubectl apply -f helmrepository.yaml + ``` + +2. Run `kubectl get helmrepository` to see the HelmRepository: + + ```console + NAME URL READY STATUS AGE + podinfo https://stefanprodan.github.io/podinfo True stored artifact for revision '83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111' 4s + ``` + +3. Run `kubectl describe helmrepository podinfo` to see the [Artifact](#artifact) + and [Conditions](#conditions) in the HelmRepository's Status: + + ```console + ... + Status: + Artifact: + Checksum: 83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111 + Last Update Time: 2022-02-04T09:55:58Z + Path: helmrepository/default/podinfo/index-83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111.yaml + Revision: 83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111 + URL: http://source-controller.flux-system.svc.cluster.local./helmrepository/default/podinfo/index-83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111.yaml + Conditions: + Last Transition Time: 2022-02-04T09:55:58Z + Message: stored artifact for revision '83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111' + Observed Generation: 1 + Reason: Succeeded + Status: True + Type: Ready + Observed Generation: 1 + URL: http://source-controller.flux-system.svc.cluster.local./helmrepository/default/podinfo/index.yaml + Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal NewArtifact 1m source-controller stored artifact for revision '83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111' + ``` + +## Writing a HelmRepository spec + +As with all other Kubernetes config, a HelmRepository needs `apiVersion`, +`kind`, and `metadata` fields. The name of a HelmRepository object must be a +valid [DNS subdomain name](https://kubernetes.io/docs/concepts/overview/working-with-objects/names#dns-subdomain-names). + +A HelmRepository also needs a +[`.spec` section](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status). + +### Interval + +`.spec.interval` is a required field that specifies the interval which the +Helm repository index must be consulted at. + +After successfully reconciling a HelmRepository object, the source-controller +requeues the object for inspection after the specified interval. The value +must be in a [Go recognized duration string format](https://pkg.go.dev/time#ParseDuration), +e.g. `10m0s` to fetch the HelmRepository index YAML every 10 minutes. + +If the `.metadata.generation` of a resource changes (due to e.g. applying a +change to the spec), this is handled instantly outside the interval window. + +### URL + +`.spec.url` is a required field that specifies the HTTP/S address of the Helm +repository. For Helm repositories which require authentication, see +[Secret reference](#secret-reference). + +### Timeout + +`.spec.timeout` is an optional field to specify a timeout for the fetch +operation. The value must be in a +[Go recognized duration string format](https://pkg.go.dev/time#ParseDuration), +e.g. `1m30s` for a timeout of one minute and thirty seconds. The default value +is `60s`. + +### Secret reference + +`.spec.secretRef.name` is an optional field to specify a name reference to a +Secret in the same namespace as the HelmRepository, containing authentication +credentials for the repository. + +#### Basic access authentication + +To authenticate towards a Helm repository using basic access authentication +(in other words: using a username and password), the referenced Secret is +expected to contain `.data.username` and `.data.password` values. + +For example: + +```yaml +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmRepository +metadata: + name: example + namespace: default +spec: + interval: 5m0s + url: https://example.com + secretRef: + name: example-user +--- +apiVersion: v1 +kind: Secret +metadata: + name: example-user + namespace: default +stringData: + username: example + password: 123456 +``` + +#### TLS authentication + +To provide TLS credentials to use while connecting with the Helm repository, +the referenced Secret is expected to contain `.data.certFile` and +`.data.keyFile`, and/or `.data.caFile` values. + +For example: + +```yaml +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmRepository +metadata: + name: example + namespace: default +spec: + interval: 5m0s + url: https://example.com + secretRef: + name: example-tls +--- +apiVersion: v1 +kind: Secret +metadata: + name: example-tls + namespace: default +data: + certFile: + keyFile: + # NOTE: Can be supplied without the above values + caFile: +``` + +### Pass credentials + +`.spec.passCredentials` is an optional field to allow the credentials from the +[Secret reference](#secret-reference) to be passed on to a host that does not +match the host as defined in URL. This may for example be required if the host +advertised chart URLs in the index differ from the specified URL. + +Enabling this should be done with caution, as it can potentially result in +credentials getting stolen in a man-in-the-middle attack. + +### Suspend + +`.spec.suspend` is an optional field to suspend the reconciliation of a +HelmRepository. When set to `true`, the controller will stop reconciling the +HelmRepository, and changes to the resource or the Helm repository index will +not result in a new Artifact. When the field is set to `false` or removed, it +will resume. + +For practical information, see +[suspending and resuming](#suspending-and-resuming). + +## Working with HelmRepositories + +### Triggering a reconcile + +To manually tell the source-controller to reconcile a HelmRepository outside the +[specified interval window](#interval), a HelmRepository can be annotated with +`reconcile.fluxcd.io/requestedAt: `. Annotating the resource +queues the object for reconciliation if the `` differs from +the last value the controller acted on, as reported in +[`.status.lastHandledReconcileAt`](#last-handled-reconcile-at). + +Using `kubectl`: + +```sh +kubectl annotate --overwrite helmrepository/ reconcile.fluxcd.io/requestedAt="$(date +%s)" +``` + +Using `flux`: + +```sh +flux reconcile source helm +``` + +### Waiting for `Ready` + +When a change is applied, it is possible to wait for the HelmRepository to +reach a [ready state](#ready-helmrepository) using `kubectl`: + +```sh +kubectl wait helmrepository/ --for=condition=ready --timeout=1m +``` + +### Suspending and resuming + +When you find yourself in a situation where you temporarily want to pause the +reconciliation of a HelmRepository, you can suspend it using the +[`.spec.suspend` field](#suspend). + +#### Suspend a HelmRepository + +In your YAML declaration: + +```yaml +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmRepository +metadata: + name: +spec: + suspend: true +``` + +Using `kubectl`: + +```sh +kubectl patch helmrepository -p '{\"spec\": {\"suspend\" : true }}' +``` + +Using `flux`: + +```sh +flux suspend source helm +``` + +**Note:** When a HelmRepository has an Artifact and is suspended, and this +Artifact later disappears from the storage due to e.g. the source-controller +Pod being evicted from a Node, this will not be reflected in the +HelmRepository's Status until it is resumed. + +#### Resume a HelmRepository + +In your YAML declaration, comment out (or remove) the field: + +```yaml +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmRepository +metadata: + name: +spec: + # suspend: true +``` + +**Note:** Setting the field value to `false` has the same effect as removing +it, but does not allow for "hot patching" using e.g. `kubectl` while practicing +GitOps; as the manually applied patch would be overwritten by the declared +state in Git. + +Using `kubectl`: + +```sh +kubectl patch helmrepository -p '{\"spec\" : {\"suspend\" : false }}' +``` + +Using `flux`: + +```sh +flux resume source helm +``` + +### Debugging a HelmRepository + +There are several ways to gather information about a HelmRepository for debugging +purposes. + +#### Describe the HelmRepository + +Describing a HelmRepository using `kubectl describe helmrepository ` +displays the latest recorded information for the resource in the `Status` and +`Events` sections: + +```console +... +Status: +... + Conditions: + Last Transition Time: 2022-02-04T13:41:56Z + Message: failed to construct Helm client: scheme "invalid" not supported + Observed Generation: 2 + Reason: Failed + Status: True + Type: Stalled + Last Transition Time: 2022-02-04T13:41:56Z + Message: failed to construct Helm client: scheme "invalid" not supported + Observed Generation: 2 + Reason: Failed + Status: False + Type: Ready + Last Transition Time: 2022-02-04T13:41:56Z + Message: failed to construct Helm client: scheme "invalid" not supported + Observed Generation: 2 + Reason: Failed + Status: True + Type: FetchFailed + Observed Generation: 2 + URL: http://source-controller.source-system.svc.cluster.local./helmrepository/default/podinfo/index.yaml +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Warning Failed 6s source-controller failed to construct Helm client: scheme "invalid" not supported +``` + +#### Trace emitted Events + +To view events for specific HelmRepository(s), `kubectl get events` can be used in +combination with `--field-sector` to list the Events for specific objects. +For example, running + +```sh +kubectl get events --field-selector involvedObject.kind=HelmRepository,involvedObject.name= +``` + +lists + +```console +LAST SEEN TYPE REASON OBJECT MESSAGE +107s Warning Failed helmrepository/ failed to construct Helm client: scheme "invalid" not supported +7s Normal NewArtifact helmrepository/ stored artifact for revision '83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111' +``` + +Besides being reported in Events, the reconciliation errors are also logged by +the controller. The Flux CLI offer commands for filtering the logs for a +specific HelmRepository, e.g. `flux logs --level=error --kind=HelmRepository --name=`. + +## HelmRepository Status + +### Artifact + +The HelmRepository reports the last fetched repository index as an Artifact +object in the `.status.artifact` of the resource. + +The Artifact file is an exact copy of the Helm repository index YAML +(`index-.yaml`) as fetched, and can be retrieved in-cluster from the +`.status.artifact.url` HTTP address. + +#### Artifact example + +```yaml +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmRepository +metadata: + name: +status: + artifact: + checksum: 83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111 + lastUpdateTime: "2022-02-04T09:55:58Z" + path: helmrepository///index-83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111.yaml + revision: 83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111 + url: http://source-controller.flux-system.svc.cluster.local./helmrepository///index-83a3c595163a6ff0333e0154c790383b5be441b9db632cb36da11db1c4ece111.yaml +``` + +### Conditions + +A HelmRepository enters various states during its lifecycle, reflected as [Kubernetes +Conditions][typical-status-properties]. +It can be [reconciling](#reconciling-helmrepository) while fetching the +repository index, it can be [ready](#ready-helmrepository), it can +[fail during reconciliation](#failed-helmrepository), or it can +[stall](#stalled-helmrepository). + +The HelmRepository API is compatible with the [kstatus +specification][kstatus-spec], +and reports `Reconciling` and `Stalled` conditions where applicable to +provide better (timeout) support to solutions polling the HelmRepository to become +`Ready`. + +#### Reconciling HelmRepository + +The source-controller marks a HelmRepository as _reconciling_ when one of the following +is true: + +- There is no current Artifact for the HelmRepository, or the reported Artifact + is determined to have disappeared from the storage. +- The generation of the HelmRepository is newer than the [Observed + Generation](#observed-generation). +- The newly fetched Artifact revision differs from the current Artifact. + +When the HelmRepository is "reconciling", the `Ready` Condition status becomes +`False`, and the controller adds a Condition with the following attributes to +the HelmRepository's `.status.conditions`: + +- `type: Reconciling` +- `status: "True"` +- `reason: NewGeneration` | `reason: NoArtifact` | `reason: NewRevision` + +If the reconciling state is due to a new revision, it adds an additional +Condition with the following attributes: + +- `type: ArtifactOutdated` +- `status: "True"` +- `reason: NewRevision` + +Both Conditions have a ["negative polarity"][typical-status-properties], +and are only present on the HelmRepository while their status value is `"True"`. + +#### Ready HelmRepository + +The source-controller marks a HelmRepository as _ready_ when it has the following +characteristics: + +- The HelmRepository reports an [Artifact](#artifact). +- The reported Artifact exists in the controller's Artifact storage. +- The controller was able to fetch the Helm repository index using the current + spec. +- The revision of the reported Artifact is up-to-date with the latest + revision of the Helm repository. + +When the HelmRepository is "ready", the controller sets a Condition with the following +attributes in the HelmRepository's `.status.conditions`: + +- `type: Ready` +- `status: "True"` +- `reason: Succeeded` + +This `Ready` Condition will retain a status value of `"True"` until the +HelmRepository is marked as [reconciling](#reconciling-helmrepository), or e.g. +a [transient error](#failed-helmrepository) occurs due to a temporary network +issue. + +#### Failed HelmRepository + +The source-controller may get stuck trying to produce an Artifact for a +HelmRepository without completing. This can occur due to some of the following +factors: + +- The Helm repository [URL](#url) is temporarily unavailable. +- The [Secret reference](#secret-reference) contains a reference to a + non-existing Secret. +- The credentials in the referenced Secret are invalid. +- The HelmRepository spec contains a generic misconfiguration. + +When this happens, the controller sets the `Ready` Condition status to `False`, +and adds a Condition with the following attributes to the HelmRepository's +`.status.conditions`: + +- `type: FetchFailed` +- `status: "True"` +- `reason: AuthenticationFailed` | `reason: IndexationFailed` | `reason: Failed` + +This condition has a ["negative polarity"][typical-status-properties], +and is only present on the HelmRepository while the status value is `"True"`. + +While the HelmRepository has this Condition, the controller will continue to +attempt to produce an Artifact for the resource with an exponential backoff, +until it succeeds and the HelmRepository is marked as [ready](#ready-helmrepository). + +Note that a HelmRepository can be [reconciling](#reconciling-helmrepository) +while failing at the same time, for example due to a newly introduced +configuration issue in the HelmRepository spec. + +#### Stalled HelmRepository + +The source-controller can mark a HelmRepository as _stalled_ when it determines +that without changes to the spec, the reconciliation can not succeed. +For example because a Helm repository URL with an unsupported protocol is +specified. + +When this happens, the controller sets the same Conditions as when it +[fails](#failed-helmrepository), but adds another Condition with the following +attributes to the HelmRepository's +`.status.conditions`: + +- `type: Stalled` +- `status: "True"` +- `reason: URLInvalid` + +While the HelmRepository has this Condition, the controller will not requeue +the resource any further, and will stop reconciling the resource until a change +to the spec is made. + +### Observed Generation + +The source-controller reports an [observed generation][typical-status-properties] +in the HelmRepository's `.status.observedGeneration`. The observed generation is +the latest `.metadata.generation` which resulted in either a [ready state](#ready-helmrepository), +or stalled due to error it can not recover from without human intervention. + +### Last Handled Reconcile At + +The source-controller reports the last `reconcile.fluxcd.io/requestedAt` +annotation value it acted on in the `.status.lastHandledReconcileAt` field. + +For practical information about this field, see [triggering a +reconcile](#triggering-a-reconcile). + +[typical-status-properties]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties +[kstatus-spec]: https://github.com/kubernetes-sigs/cli-utils/tree/master/pkg/kstatus
(Optional) -

ObservedGeneration is the last observed generation.

+

ObservedGeneration is the last observed generation of the HelmRepository +object.

(Optional) -

URL is the fetch link for the last index fetched.

+

URL is the dynamic fetch link for the latest Artifact. +It is provided on a “best effort” basis, and using the precise +HelmRepositoryStatus.Artifact data is recommended.

(Optional) -

Artifact represents the output of the last successful repository sync.

+

Artifact represents the last successful HelmRepository reconciliation.