Skip to content

Commit 0c904a1

Browse files
committed
Watch chart sources for revision changes
To enqueue a new reconciliation for the HelmChart sources as soon as the revision of their upstream source changes. Signed-off-by: Hidde Beydals <[email protected]>
1 parent fdbd71b commit 0c904a1

File tree

3 files changed

+182
-2
lines changed

3 files changed

+182
-2
lines changed

api/v1beta1/source.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import (
44
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
55
)
66

7+
const (
8+
// SourceIndexKey is the key used for indexing resources based on
9+
// their sources.
10+
SourceIndexKey string = ".metadata.source"
11+
)
12+
713
// Source interface must be supported by all API types.
814
// +k8s:deepcopy-gen=false
915
type Source interface {

controllers/helmchart_controller.go

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,13 @@ import (
4040
kuberecorder "k8s.io/client-go/tools/record"
4141
"k8s.io/client-go/tools/reference"
4242
ctrl "sigs.k8s.io/controller-runtime"
43+
"sigs.k8s.io/controller-runtime/pkg/builder"
4344
"sigs.k8s.io/controller-runtime/pkg/client"
4445
"sigs.k8s.io/controller-runtime/pkg/controller"
4546
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
47+
"sigs.k8s.io/controller-runtime/pkg/handler"
48+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
49+
"sigs.k8s.io/controller-runtime/pkg/source"
4650

4751
"github.com/fluxcd/pkg/runtime/events"
4852
"github.com/fluxcd/pkg/runtime/metrics"
@@ -208,10 +212,28 @@ func (r *HelmChartReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts
208212
r.indexHelmRepositoryByURL); err != nil {
209213
return fmt.Errorf("failed setting index fields: %w", err)
210214
}
215+
if err := mgr.GetCache().IndexField(context.TODO(), &sourcev1.HelmChart{}, sourcev1.SourceIndexKey,
216+
r.indexHelmChartBySource); err != nil {
217+
return fmt.Errorf("failed setting index fields: %w", err)
218+
}
211219

212220
return ctrl.NewControllerManagedBy(mgr).
213-
For(&sourcev1.HelmChart{}).
214-
WithEventFilter(predicates.ChangePredicate{}).
221+
For(&sourcev1.HelmChart{}, builder.WithPredicates(predicates.ChangePredicate{})).
222+
Watches(
223+
&source.Kind{Type: &sourcev1.HelmRepository{}},
224+
&handler.EnqueueRequestsFromMapFunc{ToRequests: handler.ToRequestsFunc(r.requestsForHelmRepositoryChange)},
225+
builder.WithPredicates(SourceRevisionChangePredicate{}),
226+
).
227+
Watches(
228+
&source.Kind{Type: &sourcev1.GitRepository{}},
229+
&handler.EnqueueRequestsFromMapFunc{ToRequests: handler.ToRequestsFunc(r.requestsForGitRepositoryChange)},
230+
builder.WithPredicates(SourceRevisionChangePredicate{}),
231+
).
232+
Watches(
233+
&source.Kind{Type: &sourcev1.Bucket{}},
234+
&handler.EnqueueRequestsFromMapFunc{ToRequests: handler.ToRequestsFunc(r.requestsForBucketChange)},
235+
builder.WithPredicates(SourceRevisionChangePredicate{}),
236+
).
215237
WithOptions(controller.Options{MaxConcurrentReconciles: opts.MaxConcurrentReconciles}).
216238
Complete(r)
217239
}
@@ -726,6 +748,14 @@ func (r *HelmChartReconciler) indexHelmRepositoryByURL(o runtime.Object) []strin
726748
return nil
727749
}
728750

751+
func (r *HelmChartReconciler) indexHelmChartBySource(o runtime.Object) []string {
752+
hc, ok := o.(*sourcev1.HelmChart)
753+
if !ok {
754+
panic(fmt.Sprintf("Expected a HelmChart, got %T", o))
755+
}
756+
return []string{fmt.Sprintf("%s/%s", hc.Spec.SourceRef.Kind, hc.Spec.SourceRef.Name)}
757+
}
758+
729759
func (r *HelmChartReconciler) resolveDependencyRepository(ctx context.Context, dep *helmchart.Dependency, namespace string) (*sourcev1.HelmRepository, error) {
730760
url := helm.NormalizeChartRepositoryURL(dep.Repository)
731761
if url == "" {
@@ -766,3 +796,84 @@ func (r *HelmChartReconciler) getHelmRepositorySecret(ctx context.Context, repos
766796

767797
return nil, nil
768798
}
799+
800+
func (r *HelmChartReconciler) requestsForHelmRepositoryChange(obj handler.MapObject) []reconcile.Request {
801+
repo, ok := obj.Object.(*sourcev1.HelmRepository)
802+
if !ok {
803+
panic(fmt.Sprintf("Expected a HelmRepository, got %T", repo))
804+
}
805+
// If we do not have an artifact, we have no requests to make
806+
if repo.GetArtifact() == nil {
807+
return nil
808+
}
809+
810+
ctx := context.Background()
811+
var list sourcev1.HelmRepositoryList
812+
if err := r.List(ctx, &list, client.MatchingFields{
813+
sourcev1.SourceIndexKey: fmt.Sprintf("%s/%s", repo.Kind, repo.Name),
814+
}); err != nil {
815+
r.Log.Error(err, "failed to list HelmCharts for HelmRepository")
816+
return nil
817+
}
818+
819+
var reqs []reconcile.Request
820+
for _, i := range list.Items {
821+
req := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: i.GetNamespace(), Name: i.GetName()}}
822+
reqs = append(reqs, req)
823+
}
824+
return reqs
825+
}
826+
827+
func (r *HelmChartReconciler) requestsForGitRepositoryChange(obj handler.MapObject) []reconcile.Request {
828+
repo, ok := obj.Object.(*sourcev1.GitRepository)
829+
if !ok {
830+
panic(fmt.Sprintf("Expected a GitRepository, got %T", repo))
831+
}
832+
// If we do not have an artifact, we have no requests to make
833+
if repo.GetArtifact() == nil {
834+
return nil
835+
}
836+
837+
ctx := context.Background()
838+
var list sourcev1.HelmChartList
839+
if err := r.List(ctx, &list, client.MatchingFields{
840+
sourcev1.SourceIndexKey: fmt.Sprintf("%s/%s", repo.Kind, repo.Name),
841+
}); err != nil {
842+
r.Log.Error(err, "failed to list HelmCharts for GitRepository")
843+
return nil
844+
}
845+
846+
var reqs []reconcile.Request
847+
for _, i := range list.Items {
848+
req := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: i.GetNamespace(), Name: i.GetName()}}
849+
reqs = append(reqs, req)
850+
}
851+
return reqs
852+
}
853+
854+
func (r *HelmChartReconciler) requestsForBucketChange(obj handler.MapObject) []reconcile.Request {
855+
bucket, ok := obj.Object.(*sourcev1.Bucket)
856+
if !ok {
857+
panic(fmt.Sprintf("Expected a Bucket, got %T", bucket))
858+
}
859+
// If we do not have an artifact, we have no requests to make
860+
if bucket.GetArtifact() == nil {
861+
return nil
862+
}
863+
864+
ctx := context.Background()
865+
var list sourcev1.HelmChartList
866+
if err := r.List(ctx, &list, client.MatchingFields{
867+
sourcev1.SourceIndexKey: fmt.Sprintf("%s/%s", bucket.Kind, bucket.Name),
868+
}); err != nil {
869+
r.Log.Error(err, "failed to list HelmCharts for Bucket")
870+
return nil
871+
}
872+
873+
var reqs []reconcile.Request
874+
for _, i := range list.Items {
875+
req := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: i.GetNamespace(), Name: i.GetName()}}
876+
reqs = append(reqs, req)
877+
}
878+
return reqs
879+
}

controllers/source_predicate.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
Copyright 2020 The Flux authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controllers
18+
19+
import (
20+
"sigs.k8s.io/controller-runtime/pkg/event"
21+
"sigs.k8s.io/controller-runtime/pkg/predicate"
22+
23+
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
24+
)
25+
26+
type SourceRevisionChangePredicate struct {
27+
predicate.Funcs
28+
}
29+
30+
func (SourceRevisionChangePredicate) Update(e event.UpdateEvent) bool {
31+
if e.MetaOld == nil || e.MetaNew == nil {
32+
return false
33+
}
34+
35+
oldSource, ok := e.ObjectOld.(sourcev1.Source)
36+
if !ok {
37+
return false
38+
}
39+
40+
newSource, ok := e.ObjectNew.(sourcev1.Source)
41+
if !ok {
42+
return false
43+
}
44+
45+
if oldSource.GetArtifact() == nil && newSource.GetArtifact() != nil {
46+
return true
47+
}
48+
49+
if oldSource.GetArtifact() != nil && newSource.GetArtifact() != nil &&
50+
oldSource.GetArtifact().Revision != newSource.GetArtifact().Revision {
51+
return true
52+
}
53+
54+
return false
55+
}
56+
57+
func (SourceRevisionChangePredicate) Create(e event.CreateEvent) bool {
58+
return false
59+
}
60+
61+
func (SourceRevisionChangePredicate) Delete(e event.DeleteEvent) bool {
62+
return false
63+
}

0 commit comments

Comments
 (0)