@@ -17,14 +17,14 @@ limitations under the License.
1717package controllers
1818
1919import (
20- "bytes"
2120 "context"
2221 "fmt"
2322 "net/url"
23+ "os"
2424 "time"
2525
2626 "github.com/go-logr/logr"
27- "helm.sh/helm/v3/pkg/getter"
27+ helmgetter "helm.sh/helm/v3/pkg/getter"
2828 corev1 "k8s.io/api/core/v1"
2929 apimeta "k8s.io/apimachinery/pkg/api/meta"
3030 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -37,15 +37,15 @@ import (
3737 "sigs.k8s.io/controller-runtime/pkg/controller"
3838 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3939 "sigs.k8s.io/controller-runtime/pkg/predicate"
40- "sigs.k8s.io/yaml"
4140
4241 "github.com/fluxcd/pkg/apis/meta"
4342 "github.com/fluxcd/pkg/runtime/events"
4443 "github.com/fluxcd/pkg/runtime/metrics"
4544 "github.com/fluxcd/pkg/runtime/predicates"
4645
4746 sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
48- "github.com/fluxcd/source-controller/internal/helm"
47+ "github.com/fluxcd/source-controller/internal/helm/getter"
48+ "github.com/fluxcd/source-controller/internal/helm/repository"
4949)
5050
5151// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmrepositories,verbs=get;list;watch;create;update;patch;delete
@@ -58,7 +58,7 @@ type HelmRepositoryReconciler struct {
5858 client.Client
5959 Scheme * runtime.Scheme
6060 Storage * Storage
61- Getters getter .Providers
61+ Getters helmgetter .Providers
6262 EventRecorder kuberecorder.EventRecorder
6363 ExternalEventRecorder * events.Recorder
6464 MetricsRecorder * metrics.Recorder
@@ -170,96 +170,108 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque
170170 return ctrl.Result {RequeueAfter : repository .GetInterval ().Duration }, nil
171171}
172172
173- func (r * HelmRepositoryReconciler ) reconcile (ctx context.Context , repository sourcev1.HelmRepository ) (sourcev1.HelmRepository , error ) {
174- clientOpts := []getter .Option {
175- getter .WithURL (repository .Spec .URL ),
176- getter .WithTimeout (repository .Spec .Timeout .Duration ),
177- getter .WithPassCredentialsAll (repository .Spec .PassCredentials ),
173+ func (r * HelmRepositoryReconciler ) reconcile (ctx context.Context , repo sourcev1.HelmRepository ) (sourcev1.HelmRepository , error ) {
174+ clientOpts := []helmgetter .Option {
175+ helmgetter .WithURL (repo .Spec .URL ),
176+ helmgetter .WithTimeout (repo .Spec .Timeout .Duration ),
177+ helmgetter .WithPassCredentialsAll (repo .Spec .PassCredentials ),
178178 }
179- if repository .Spec .SecretRef != nil {
179+ if repo .Spec .SecretRef != nil {
180180 name := types.NamespacedName {
181- Namespace : repository .GetNamespace (),
182- Name : repository .Spec .SecretRef .Name ,
181+ Namespace : repo .GetNamespace (),
182+ Name : repo .Spec .SecretRef .Name ,
183183 }
184184
185185 var secret corev1.Secret
186186 err := r .Client .Get (ctx , name , & secret )
187187 if err != nil {
188188 err = fmt .Errorf ("auth secret error: %w" , err )
189- return sourcev1 .HelmRepositoryNotReady (repository , sourcev1 .AuthenticationFailedReason , err .Error ()), err
189+ return sourcev1 .HelmRepositoryNotReady (repo , sourcev1 .AuthenticationFailedReason , err .Error ()), err
190190 }
191191
192- opts , cleanup , err := helm .ClientOptionsFromSecret (secret )
192+ authDir , err := os .MkdirTemp ("" , repo .Kind + "-" + repo .Namespace + "-" + repo .Name + "-" )
193+ if err != nil {
194+ err = fmt .Errorf ("failed to create temporary working directory for credentials: %w" , err )
195+ return sourcev1 .HelmRepositoryNotReady (repo , sourcev1 .AuthenticationFailedReason , err .Error ()), err
196+ }
197+ defer os .RemoveAll (authDir )
198+
199+ opts , err := getter .ClientOptionsFromSecret (authDir , secret )
193200 if err != nil {
194201 err = fmt .Errorf ("auth options error: %w" , err )
195- return sourcev1 .HelmRepositoryNotReady (repository , sourcev1 .AuthenticationFailedReason , err .Error ()), err
202+ return sourcev1 .HelmRepositoryNotReady (repo , sourcev1 .AuthenticationFailedReason , err .Error ()), err
196203 }
197- defer cleanup ()
198204 clientOpts = append (clientOpts , opts ... )
199205 }
200206
201- chartRepo , err := helm .NewChartRepository (repository .Spec .URL , r .Getters , clientOpts )
207+ chartRepo , err := repository .NewChartRepository (repo .Spec .URL , "" , r .Getters , clientOpts )
202208 if err != nil {
203209 switch err .(type ) {
204210 case * url.Error :
205- return sourcev1 .HelmRepositoryNotReady (repository , sourcev1 .URLInvalidReason , err .Error ()), err
211+ return sourcev1 .HelmRepositoryNotReady (repo , sourcev1 .URLInvalidReason , err .Error ()), err
206212 default :
207- return sourcev1 .HelmRepositoryNotReady (repository , sourcev1 .IndexationFailedReason , err .Error ()), err
213+ return sourcev1 .HelmRepositoryNotReady (repo , sourcev1 .IndexationFailedReason , err .Error ()), err
208214 }
209215 }
210- if err := chartRepo .DownloadIndex (); err != nil {
216+ checksum , err := chartRepo .CacheIndex ()
217+ if err != nil {
211218 err = fmt .Errorf ("failed to download repository index: %w" , err )
212- return sourcev1 .HelmRepositoryNotReady (repository , sourcev1 .IndexationFailedReason , err .Error ()), err
219+ return sourcev1 .HelmRepositoryNotReady (repo , sourcev1 .IndexationFailedReason , err .Error ()), err
213220 }
221+ defer chartRepo .RemoveCache ()
214222
215- indexBytes , err := yaml .Marshal (& chartRepo .Index )
216- if err != nil {
217- return sourcev1 .HelmRepositoryNotReady (repository , sourcev1 .StorageOperationFailedReason , err .Error ()), err
218- }
219- hash := r .Storage .Checksum (bytes .NewReader (indexBytes ))
220- artifact := r .Storage .NewArtifactFor (repository .Kind ,
221- repository .ObjectMeta .GetObjectMeta (),
222- hash ,
223- fmt .Sprintf ("index-%s.yaml" , hash ))
224- // return early on unchanged index
225- if apimeta .IsStatusConditionTrue (repository .Status .Conditions , meta .ReadyCondition ) && repository .GetArtifact ().HasRevision (artifact .Revision ) {
226- if artifact .URL != repository .GetArtifact ().URL {
227- r .Storage .SetArtifactURL (repository .GetArtifact ())
228- repository .Status .URL = r .Storage .SetHostname (repository .Status .URL )
223+ artifact := r .Storage .NewArtifactFor (repo .Kind ,
224+ repo .ObjectMeta .GetObjectMeta (),
225+ "" ,
226+ fmt .Sprintf ("index-%s.yaml" , checksum ))
227+
228+ // Return early on unchanged index
229+ if apimeta .IsStatusConditionTrue (repo .Status .Conditions , meta .ReadyCondition ) &&
230+ (repo .GetArtifact () != nil && repo .GetArtifact ().Checksum == checksum ) {
231+ if artifact .URL != repo .GetArtifact ().URL {
232+ r .Storage .SetArtifactURL (repo .GetArtifact ())
233+ repo .Status .URL = r .Storage .SetHostname (repo .Status .URL )
229234 }
230- return repository , nil
235+ return repo , nil
236+ }
237+
238+ // Load the cached repository index to ensure it passes validation
239+ if err := chartRepo .LoadFromCache (); err != nil {
240+ return sourcev1 .HelmRepositoryNotReady (repo , sourcev1 .IndexationFailedReason , err .Error ()), err
231241 }
242+ // The repository checksum is the SHA256 of the loaded bytes, after sorting
243+ artifact .Revision = chartRepo .Checksum
244+ chartRepo .Unload ()
232245
233- // create artifact dir
246+ // Create artifact dir
234247 err = r .Storage .MkdirAll (artifact )
235248 if err != nil {
236249 err = fmt .Errorf ("unable to create repository index directory: %w" , err )
237- return sourcev1 .HelmRepositoryNotReady (repository , sourcev1 .StorageOperationFailedReason , err .Error ()), err
250+ return sourcev1 .HelmRepositoryNotReady (repo , sourcev1 .StorageOperationFailedReason , err .Error ()), err
238251 }
239252
240- // acquire lock
253+ // Acquire lock
241254 unlock , err := r .Storage .Lock (artifact )
242255 if err != nil {
243256 err = fmt .Errorf ("unable to acquire lock: %w" , err )
244- return sourcev1 .HelmRepositoryNotReady (repository , sourcev1 .StorageOperationFailedReason , err .Error ()), err
257+ return sourcev1 .HelmRepositoryNotReady (repo , sourcev1 .StorageOperationFailedReason , err .Error ()), err
245258 }
246259 defer unlock ()
247260
248- // save artifact to storage
249- if err := r .Storage .AtomicWriteFile (& artifact , bytes .NewReader (indexBytes ), 0644 ); err != nil {
250- err = fmt .Errorf ("unable to write repository index file: %w" , err )
251- return sourcev1 .HelmRepositoryNotReady (repository , sourcev1 .StorageOperationFailedReason , err .Error ()), err
261+ // Save artifact to storage
262+ if err = r .Storage .CopyFromPath (& artifact , chartRepo .CachePath ); err != nil {
263+ return sourcev1 .HelmRepositoryNotReady (repo , sourcev1 .StorageOperationFailedReason , err .Error ()), err
252264 }
253265
254- // update index symlink
266+ // Update index symlink
255267 indexURL , err := r .Storage .Symlink (artifact , "index.yaml" )
256268 if err != nil {
257269 err = fmt .Errorf ("storage error: %w" , err )
258- return sourcev1 .HelmRepositoryNotReady (repository , sourcev1 .StorageOperationFailedReason , err .Error ()), err
270+ return sourcev1 .HelmRepositoryNotReady (repo , sourcev1 .StorageOperationFailedReason , err .Error ()), err
259271 }
260272
261273 message := fmt .Sprintf ("Fetched revision: %s" , artifact .Revision )
262- return sourcev1 .HelmRepositoryReady (repository , artifact , indexURL , sourcev1 .IndexationSucceededReason , message ), nil
274+ return sourcev1 .HelmRepositoryReady (repo , artifact , indexURL , sourcev1 .IndexationSucceededReason , message ), nil
263275}
264276
265277func (r * HelmRepositoryReconciler ) reconcileDelete (ctx context.Context , repository sourcev1.HelmRepository ) (ctrl.Result , error ) {
0 commit comments