Trigger kustomization apply on source changes

- detect revision changes in Git repositories
- index kustomizations based on source
- trigger kustomizations on revision changes
This commit is contained in:
stefanprodan 2020-04-16 10:40:28 +03:00
parent eb7fdfe98d
commit ee74f8e329
6 changed files with 152 additions and 46 deletions

View File

@ -83,6 +83,16 @@ func KustomizationReadyMessage(kustomization Kustomization) string {
return ""
}
const (
// SyncAtAnnotation is the annotation used for triggering a
// sync outside of the specified schedule.
SyncAtAnnotation string = "kustomize.fluxcd.io/syncAt"
// SourceIndexKey is the key used for indexing kustomizations
// based on their sources.
SourceIndexKey string = ".metadata.source"
)
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""

View File

@ -18,12 +18,15 @@ package controllers
import (
"context"
"time"
"github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
)
@ -38,16 +41,51 @@ type GitRepositoryWatcher struct {
// +kubebuilder:rbac:groups=source.fluxcd.io,resources=gitrepositories/status,verbs=get
func (r *GitRepositoryWatcher) Reconcile(req ctrl.Request) (ctrl.Result, error) {
_ = context.Background()
_ = r.Log.WithValues("gitrepository", req.NamespacedName)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// your logic here
var repo sourcev1.GitRepository
if err := r.Get(ctx, req.NamespacedName, &repo); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log := r.Log.WithValues(repo.Kind, req.NamespacedName)
log.Info("New artifact detected")
// get the list of kustomizations that are using this Git repository
var list kustomizev1.KustomizationList
if err := r.List(ctx, &list, client.InNamespace(req.Namespace),
client.MatchingFields{kustomizev1.SourceIndexKey: req.Name}); err != nil {
log.Error(err, "unable to list kustomizations")
return ctrl.Result{}, err
}
// trigger apply for each kustomization using this Git repository
for _, kustomization := range list.Items {
kustomization.Annotations[kustomizev1.SyncAtAnnotation] = metav1.Now().String()
if err := r.Update(ctx, &kustomization); err != nil {
log.Error(err, "unable to annotate kustomization", "kustomization", kustomization.GetName())
}
log.Info("Run kustomization", "kustomization", kustomization.GetName())
}
return ctrl.Result{}, nil
}
func (r *GitRepositoryWatcher) SetupWithManager(mgr ctrl.Manager) error {
// create a kustomization index based on Git repository name
err := mgr.GetFieldIndexer().IndexField(&kustomizev1.Kustomization{}, kustomizev1.SourceIndexKey,
func(rawObj runtime.Object) []string {
k := rawObj.(*kustomizev1.Kustomization)
return []string{k.Spec.GitRepositoryRef.Name}
},
)
if err != nil {
return err
}
return ctrl.NewControllerManagedBy(mgr).
For(&sourcev1.GitRepository{}).
WithEventFilter(GitRepositoryRevisionChangePredicate{}).
Complete(r)
}

View File

@ -0,0 +1,59 @@
/*
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
)
type GitRepositoryRevisionChangePredicate struct {
predicate.Funcs
}
func (GitRepositoryRevisionChangePredicate) Update(e event.UpdateEvent) bool {
if e.MetaOld == nil || e.MetaNew == nil {
return false
}
oldRepo, ok := e.ObjectOld.(*sourcev1.GitRepository)
if !ok {
return false
}
newRepo, ok := e.ObjectNew.(*sourcev1.GitRepository)
if !ok {
return false
}
if oldRepo.Status.Artifact != nil && newRepo.Status.Artifact != nil &&
oldRepo.Status.Artifact.Revision != newRepo.Status.Artifact.Revision {
return true
}
return false
}
func (GitRepositoryRevisionChangePredicate) Create(e event.CreateEvent) bool {
return false
}
func (GitRepositoryRevisionChangePredicate) Delete(e event.DeleteEvent) bool {
return false
}

View File

@ -19,19 +19,19 @@ package controllers
import (
"context"
"fmt"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"io/ioutil"
"k8s.io/apimachinery/pkg/types"
"os"
"os/exec"
"time"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
)
// KustomizationReconciler reconciles a Kustomization object
@ -55,33 +55,6 @@ func (r *KustomizationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
log := r.Log.WithValues(kustomization.Kind, req.NamespacedName)
// try git sync
syncedKustomization, err := r.sync(ctx, *kustomization.DeepCopy())
if err != nil {
log.Error(err, "Kustomization sync failed")
return ctrl.Result{Requeue: true}, err
}
// update status
if err := r.Status().Update(ctx, &syncedKustomization); err != nil {
log.Error(err, "unable to update Kustomization status")
return ctrl.Result{Requeue: true}, err
}
log.Info("Kustomization apply succeeded", "msg", kustomizev1.KustomizationReadyMessage(syncedKustomization))
// requeue kustomization
return ctrl.Result{RequeueAfter: kustomization.Spec.Interval.Duration}, nil
}
func (r *KustomizationReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&kustomizev1.Kustomization{}).
WithEventFilter(ChangePredicate{}).
Complete(r)
}
func (r *KustomizationReconciler) sync(ctx context.Context, kustomization kustomizev1.Kustomization) (kustomizev1.Kustomization, error) {
// get artifact source
var repository sourcev1.GitRepository
repositoryName := types.NamespacedName{
@ -90,12 +63,41 @@ func (r *KustomizationReconciler) sync(ctx context.Context, kustomization kustom
}
err := r.Client.Get(ctx, repositoryName, &repository)
if err != nil {
err = fmt.Errorf("GitRepository query error: %w", err)
return kustomizev1.KustomizationNotReady(kustomization, kustomizev1.ArtifactFailedReason, err.Error()), err
log.Error(err, "GitRepository not found", "gitrepository", repositoryName)
return ctrl.Result{Requeue: true}, err
}
// try git sync
syncedKustomization, err := r.sync(ctx, *kustomization.DeepCopy(), repository)
if err != nil {
log.Error(err, "Kustomization sync failed")
}
// update status
if err := r.Status().Update(ctx, &syncedKustomization); err != nil {
log.Error(err, "unable to update Kustomization status")
return ctrl.Result{Requeue: true}, err
}
log.Info("Kustomization sync finished", "msg", kustomizev1.KustomizationReadyMessage(syncedKustomization))
// requeue kustomization
return ctrl.Result{RequeueAfter: kustomization.Spec.Interval.Duration}, nil
}
func (r *KustomizationReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&kustomizev1.Kustomization{}).
WithEventFilter(KustomizationSyncAtPredicate{}).
Complete(r)
}
func (r *KustomizationReconciler) sync(
ctx context.Context,
kustomization kustomizev1.Kustomization,
repository sourcev1.GitRepository) (kustomizev1.Kustomization, error) {
if repository.Status.Artifact == nil || repository.Status.Artifact.URL == "" {
err = fmt.Errorf("artifact not found in %s", repositoryName)
err := fmt.Errorf("artifact not found in %s", repository.GetName())
return kustomizev1.KustomizationNotReady(kustomization, kustomizev1.ArtifactFailedReason, err.Error()), err
}

View File

@ -19,15 +19,15 @@ package controllers
import (
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
)
type ChangePredicate struct {
type KustomizationSyncAtPredicate struct {
predicate.Funcs
}
// Update implements the default UpdateEvent filter for validating
// kustomization changes.
func (ChangePredicate) Update(e event.UpdateEvent) bool {
func (KustomizationSyncAtPredicate) Update(e event.UpdateEvent) bool {
if e.MetaOld == nil || e.MetaNew == nil {
// ignore objects without metadata
return false
@ -37,9 +37,9 @@ func (ChangePredicate) Update(e event.UpdateEvent) bool {
return true
}
// handle force sync
if val, ok := e.MetaNew.GetAnnotations()[ForceSyncAnnotation]; ok {
if valOld, okOld := e.MetaOld.GetAnnotations()[ForceSyncAnnotation]; okOld {
// handle syncAt annotation
if val, ok := e.MetaNew.GetAnnotations()[kustomizev1.SyncAtAnnotation]; ok {
if valOld, okOld := e.MetaOld.GetAnnotations()[kustomizev1.SyncAtAnnotation]; okOld {
if val != valOld {
return true
}
@ -50,7 +50,3 @@ func (ChangePredicate) Update(e event.UpdateEvent) bool {
return false
}
const (
ForceSyncAnnotation string = "kustomize.fluxcd.io/syncAt"
)

1
go.sum
View File

@ -537,6 +537,7 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 h1:bw9doJza/SFBEweII/rHQh338oozWyiFsBRHtrflcws=
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=