@@ -38,6 +38,7 @@ import (
3838 ctrl "sigs.k8s.io/controller-runtime"
3939 "sigs.k8s.io/controller-runtime/pkg/client"
4040 fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
41+ "sigs.k8s.io/controller-runtime/pkg/client/interceptor"
4142 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
4243
4344 kstatus "github.com/fluxcd/cli-utils/pkg/kstatus/status"
@@ -1089,6 +1090,136 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) {
10891090 }
10901091}
10911092
1093+ func TestHelmRepositoryReconciler_reconcileSource_cachedIndexFile (t * testing.T ) {
1094+ newReconciler := func (g * WithT , c client.Client ) * HelmRepositoryReconciler {
1095+ return & HelmRepositoryReconciler {
1096+ Recorder : events .NewFakeRecorder (32 , false ),
1097+ Client : c ,
1098+ Storage : testStorage ,
1099+ Getters : testGetters ,
1100+ patchOptions : getPatchOptions (helmRepositoryReadyCondition .Owned , "sc" ),
1101+ }
1102+ }
1103+
1104+ startServer := func (g * WithT , index []byte ) * helmtestserver.HelmServer {
1105+ server , err := helmtestserver .NewTempHelmServer ()
1106+ g .Expect (err ).NotTo (HaveOccurred ())
1107+ if index == nil {
1108+ g .Expect (server .PackageChart ("testdata/charts/helmchart" )).To (Succeed ())
1109+ g .Expect (server .GenerateIndex ()).To (Succeed ())
1110+ } else {
1111+ g .Expect (os .WriteFile (filepath .Join (server .Root (), "index.yaml" ), index , 0o600 )).To (Succeed ())
1112+ }
1113+ server .Start ()
1114+ return server
1115+ }
1116+
1117+ newObject := func (url string ) * sourcev1.HelmRepository {
1118+ return & sourcev1.HelmRepository {
1119+ ObjectMeta : metav1.ObjectMeta {
1120+ Name : "index-cache" ,
1121+ Namespace : "default" ,
1122+ Generation : 1 ,
1123+ },
1124+ Spec : sourcev1.HelmRepositorySpec {
1125+ Interval : metav1.Duration {Duration : interval },
1126+ Timeout : & metav1.Duration {Duration : timeout },
1127+ URL : url ,
1128+ },
1129+ }
1130+ }
1131+
1132+ chartIndexFiles := func (g * WithT , dir string ) []string {
1133+ matches , err := filepath .Glob (filepath .Join (dir , "chart-index-*.yaml" ))
1134+ g .Expect (err ).NotTo (HaveOccurred ())
1135+ return matches
1136+ }
1137+
1138+ t .Run ("invalid index removes cached file" , func (t * testing.T ) {
1139+ g := NewWithT (t )
1140+ dir := t .TempDir ()
1141+ t .Setenv ("TMPDIR" , dir )
1142+
1143+ // HTTP 200 with a body that is not a Helm index. Download succeeds
1144+ // and LoadFromPath fails.
1145+ server := startServer (g , []byte ("{}\n " ))
1146+ defer server .Stop ()
1147+ defer os .RemoveAll (server .Root ())
1148+
1149+ c := fakeclient .NewClientBuilder ().
1150+ WithScheme (testEnv .GetScheme ()).
1151+ WithStatusSubresource (& sourcev1.HelmRepository {}).
1152+ Build ()
1153+ obj := newObject (server .URL ())
1154+ g .Expect (c .Create (context .TODO (), obj )).To (Succeed ())
1155+
1156+ var chartRepo repository.ChartRepository
1157+ got , err := newReconciler (g , c ).reconcileSource (context .TODO (), patch .NewSerialPatcher (obj , c ), obj , & meta.Artifact {}, & chartRepo )
1158+
1159+ g .Expect (err ).To (HaveOccurred ())
1160+ g .Expect (err .Error ()).To (ContainSubstring ("failed to load Helm repository from index YAML" ))
1161+ g .Expect (got ).To (Equal (sreconcile .ResultEmpty ))
1162+ g .Expect (conditions .IsTrue (obj , sourcev1 .FetchFailedCondition )).To (BeTrue ())
1163+ g .Expect (chartIndexFiles (g , dir )).To (BeEmpty ())
1164+ })
1165+
1166+ t .Run ("status patch failure removes cached file" , func (t * testing.T ) {
1167+ g := NewWithT (t )
1168+ dir := t .TempDir ()
1169+ t .Setenv ("TMPDIR" , dir )
1170+
1171+ server := startServer (g , nil )
1172+ defer server .Stop ()
1173+ defer os .RemoveAll (server .Root ())
1174+
1175+ base := fakeclient .NewClientBuilder ().
1176+ WithScheme (testEnv .GetScheme ()).
1177+ WithStatusSubresource (& sourcev1.HelmRepository {}).
1178+ Build ()
1179+ c := interceptor .NewClient (base , interceptor.Funcs {
1180+ SubResourcePatch : func (context.Context , client.Client , string , client.Object , client.Patch , ... client.SubResourcePatchOption ) error {
1181+ return errors .New ("injected patch failure" )
1182+ },
1183+ })
1184+ obj := newObject (server .URL ())
1185+ g .Expect (c .Create (context .TODO (), obj )).To (Succeed ())
1186+
1187+ var chartRepo repository.ChartRepository
1188+ got , err := newReconciler (g , c ).reconcileSource (context .TODO (), patch .NewSerialPatcher (obj , c ), obj , & meta.Artifact {}, & chartRepo )
1189+
1190+ g .Expect (err ).To (HaveOccurred ())
1191+ g .Expect (err .Error ()).To (ContainSubstring ("injected patch failure" ))
1192+ g .Expect (got ).To (Equal (sreconcile .ResultEmpty ))
1193+ g .Expect (chartIndexFiles (g , dir )).To (BeEmpty ())
1194+ })
1195+
1196+ t .Run ("successful fetch keeps cached file" , func (t * testing.T ) {
1197+ g := NewWithT (t )
1198+ dir := t .TempDir ()
1199+ t .Setenv ("TMPDIR" , dir )
1200+
1201+ server := startServer (g , nil )
1202+ defer server .Stop ()
1203+ defer os .RemoveAll (server .Root ())
1204+
1205+ c := fakeclient .NewClientBuilder ().
1206+ WithScheme (testEnv .GetScheme ()).
1207+ WithStatusSubresource (& sourcev1.HelmRepository {}).
1208+ Build ()
1209+ obj := newObject (server .URL ())
1210+ g .Expect (c .Create (context .TODO (), obj )).To (Succeed ())
1211+
1212+ var chartRepo repository.ChartRepository
1213+ got , err := newReconciler (g , c ).reconcileSource (context .TODO (), patch .NewSerialPatcher (obj , c ), obj , & meta.Artifact {}, & chartRepo )
1214+ defer os .Remove (chartRepo .Path )
1215+
1216+ g .Expect (err ).NotTo (HaveOccurred ())
1217+ g .Expect (got ).To (Equal (sreconcile .ResultSuccess ))
1218+ matches := chartIndexFiles (g , dir )
1219+ g .Expect (matches ).To (Equal ([]string {chartRepo .Path }))
1220+ })
1221+ }
1222+
10921223func TestHelmRepositoryReconciler_reconcileArtifact (t * testing.T ) {
10931224 // The cache is at capacity with the previous revision and an index of
10941225 // another repository, the new revision can only be added after eviction.
0 commit comments