Skip to content

Commit 3fd81d0

Browse files
committed
GitRepo: Add observed source config in status
Replace content config checksum with explicit source config observations. It makes the observations of the controller more transparent and easier to debug. Introduces `observedIgnore`, `observedRecurseSubmodules` and `observedInclude` status fields. Signed-off-by: Sunny <[email protected]>
1 parent 62eb502 commit 3fd81d0

File tree

7 files changed

+631
-104
lines changed

7 files changed

+631
-104
lines changed

api/v1beta2/gitrepository_types.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,21 @@ type GitRepositoryStatus struct {
227227
// +optional
228228
ContentConfigChecksum string `json:"contentConfigChecksum,omitempty"`
229229

230+
// ObservedIgnore is the observed exclusion patterns used for constructing
231+
// the source artifact.
232+
// +optional
233+
ObservedIgnore *string `json:"observedIgnore,omitempty"`
234+
235+
// ObservedRecurseSubmodules is the observed resource submodules
236+
// configuration used to produce the current Artifact.
237+
// +optional
238+
ObservedRecurseSubmodules bool `json:"observedRecurseSubmodules,omitempty"`
239+
240+
// ObservedInclude is the observed list of GitRepository resources used to
241+
// to produce the current Artifact.
242+
// +optional
243+
ObservedInclude []GitRepositoryInclude `json:"observedInclude,omitempty"`
244+
230245
meta.ReconcileRequestStatus `json:",inline"`
231246
}
232247

api/v1beta2/zz_generated.deepcopy.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,44 @@ spec:
723723
the GitRepository object.
724724
format: int64
725725
type: integer
726+
observedIgnore:
727+
description: ObservedIgnore is the observed exclusion patterns used
728+
for constructing the source artifact.
729+
type: string
730+
observedInclude:
731+
description: ObservedInclude is the observed list of GitRepository
732+
resources used to to produce the current Artifact.
733+
items:
734+
description: GitRepositoryInclude specifies a local reference to
735+
a GitRepository which Artifact (sub-)contents must be included,
736+
and where they should be placed.
737+
properties:
738+
fromPath:
739+
description: FromPath specifies the path to copy contents from,
740+
defaults to the root of the Artifact.
741+
type: string
742+
repository:
743+
description: GitRepositoryRef specifies the GitRepository which
744+
Artifact contents must be included.
745+
properties:
746+
name:
747+
description: Name of the referent.
748+
type: string
749+
required:
750+
- name
751+
type: object
752+
toPath:
753+
description: ToPath specifies the path to copy contents to,
754+
defaults to the name of the GitRepositoryRef.
755+
type: string
756+
required:
757+
- repository
758+
type: object
759+
type: array
760+
observedRecurseSubmodules:
761+
description: ObservedRecurseSubmodules is the observed resource submodules
762+
configuration used to produce the current Artifact.
763+
type: boolean
726764
url:
727765
description: URL is the dynamic fetch link for the latest Artifact.
728766
It is provided on a "best effort" basis, and using the precise GitRepositoryStatus.Artifact

controllers/gitrepository_controller.go

Lines changed: 76 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ package controllers
1818

1919
import (
2020
"context"
21-
"crypto/sha256"
2221
"errors"
2322
"fmt"
2423
"os"
2524
"path/filepath"
26-
"strconv"
2725
"strings"
2826
"time"
2927

@@ -33,6 +31,7 @@ import (
3331
"k8s.io/apimachinery/pkg/runtime"
3432
"k8s.io/apimachinery/pkg/types"
3533
kuberecorder "k8s.io/client-go/tools/record"
34+
"k8s.io/utils/pointer"
3635
ctrl "sigs.k8s.io/controller-runtime"
3736
"sigs.k8s.io/controller-runtime/pkg/builder"
3837
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -507,8 +506,8 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context,
507506
// If it's a partial commit obtained from an existing artifact, check if the
508507
// reconciliation can be skipped if other configurations have not changed.
509508
if !git.IsConcreteCommit(*commit) {
510-
// Calculate content configuration checksum.
511-
if r.calculateContentConfigChecksum(obj, includes) == obj.Status.ContentConfigChecksum {
509+
// Check if the source config contributing to the artifact has changed.
510+
if !gitSourceConfigChanged(obj, includes) {
512511
ge := serror.NewGeneric(
513512
fmt.Errorf("no changes since last reconcilation: observed revision '%s'",
514513
commit.String()), sourcev1.GitOperationSucceedReason,
@@ -559,27 +558,24 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context,
559558
//
560559
// The inspection of the given data to the object is differed, ensuring any
561560
// stale observations like v1beta2.ArtifactOutdatedCondition are removed.
562-
// If the given Artifact and/or artifactSet (includes) and the content config
563-
// checksum do not differ from the object's current, it returns early.
561+
// If the given Artifact and/or artifactSet (includes) and observed source
562+
// config do not differ from the object's current, it returns early.
564563
// Source ignore patterns are loaded, and the given directory is archived while
565564
// taking these patterns into account.
566-
// On a successful archive, the Artifact, Includes and new content config
567-
// checksum in the Status of the object are set, and the symlink in the Storage
568-
// is updated to its path.
565+
// On a successful archive, the Artifact, Includes, observed ignore, recurse
566+
// submodules and observed include in the Status of the object are set, and the
567+
// symlink in the Storage is updated to its path.
569568
func (r *GitRepositoryReconciler) reconcileArtifact(ctx context.Context,
570569
obj *sourcev1.GitRepository, commit *git.Commit, includes *artifactSet, dir string) (sreconcile.Result, error) {
571570

572571
// Create potential new artifact with current available metadata
573572
artifact := r.Storage.NewArtifactFor(obj.Kind, obj.GetObjectMeta(), commit.String(), fmt.Sprintf("%s.tar.gz", commit.Hash.String()))
574573

575-
// Calculate the content config checksum.
576-
ccc := r.calculateContentConfigChecksum(obj, includes)
577-
578574
// Set the ArtifactInStorageCondition if there's no drift.
579575
defer func() {
580576
if obj.GetArtifact().HasRevision(artifact.Revision) &&
581577
!includes.Diff(obj.Status.IncludedArtifacts) &&
582-
obj.Status.ContentConfigChecksum == ccc {
578+
!gitSourceConfigChanged(obj, includes) {
583579
conditions.Delete(obj, sourcev1.ArtifactOutdatedCondition)
584580
conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason,
585581
"stored artifact for revision '%s'", artifact.Revision)
@@ -589,7 +585,7 @@ func (r *GitRepositoryReconciler) reconcileArtifact(ctx context.Context,
589585
// The artifact is up-to-date
590586
if obj.GetArtifact().HasRevision(artifact.Revision) &&
591587
!includes.Diff(obj.Status.IncludedArtifacts) &&
592-
obj.Status.ContentConfigChecksum == ccc {
588+
!gitSourceConfigChanged(obj, includes) {
593589
r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.ArtifactUpToDateReason, "artifact up-to-date with remote revision: '%s'", artifact.Revision)
594590
return sreconcile.ResultSuccess, nil
595591
}
@@ -652,10 +648,13 @@ func (r *GitRepositoryReconciler) reconcileArtifact(ctx context.Context,
652648
return sreconcile.ResultEmpty, e
653649
}
654650

655-
// Record it on the object
651+
// Record the observations on the object.
656652
obj.Status.Artifact = artifact.DeepCopy()
657653
obj.Status.IncludedArtifacts = *includes
658-
obj.Status.ContentConfigChecksum = ccc
654+
obj.Status.ContentConfigChecksum = "" // To be removed in the next API version.
655+
obj.Status.ObservedIgnore = obj.Spec.Ignore
656+
obj.Status.ObservedRecurseSubmodules = obj.Spec.RecurseSubmodules
657+
obj.Status.ObservedInclude = obj.Spec.Include
659658

660659
// Update symlink on a "best effort" basis
661660
url, err := r.Storage.Symlink(artifact, "latest.tar.gz")
@@ -825,39 +824,6 @@ func (r *GitRepositoryReconciler) fetchIncludes(ctx context.Context, obj *source
825824
return &artifacts, nil
826825
}
827826

828-
// calculateContentConfigChecksum calculates a checksum of all the
829-
// configurations that result in a change in the source artifact. It can be used
830-
// to decide if further reconciliation is needed when an artifact already exists
831-
// for a set of configurations.
832-
func (r *GitRepositoryReconciler) calculateContentConfigChecksum(obj *sourcev1.GitRepository, includes *artifactSet) string {
833-
c := []byte{}
834-
// Consider the ignore rules and recurse submodules.
835-
if obj.Spec.Ignore != nil {
836-
c = append(c, []byte(*obj.Spec.Ignore)...)
837-
}
838-
c = append(c, []byte(strconv.FormatBool(obj.Spec.RecurseSubmodules))...)
839-
840-
// Consider the included repository attributes.
841-
for _, incl := range obj.Spec.Include {
842-
c = append(c, []byte(incl.GitRepositoryRef.Name+incl.FromPath+incl.ToPath)...)
843-
}
844-
845-
// Consider the checksum and revision of all the included remote artifact.
846-
// This ensures that if the included repos get updated, this checksum changes.
847-
// NOTE: The content of an artifact may change at the same revision if the
848-
// ignore rules change. Hence, consider both checksum and revision to
849-
// capture changes in artifact checksum as well.
850-
// TODO: Fix artifactSet.Diff() to consider checksum as well.
851-
if includes != nil {
852-
for _, incl := range *includes {
853-
c = append(c, []byte(incl.Checksum)...)
854-
c = append(c, []byte(incl.Revision)...)
855-
}
856-
}
857-
858-
return fmt.Sprintf("sha256:%x", sha256.Sum256(c))
859-
}
860-
861827
// verifyCommitSignature verifies the signature of the given Git commit, if a
862828
// verification mode is specified on the object.
863829
// If the signature can not be verified or the verification fails, it records
@@ -978,3 +944,64 @@ func (r *GitRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Obj
978944
}
979945
r.Eventf(obj, eventType, reason, msg)
980946
}
947+
948+
// gitSourceConfigChanged evaluates the current spec with the observations of
949+
// the artifact in the status to determine if source configuration has changed
950+
// and requires rebuilding the artifact.
951+
func gitSourceConfigChanged(obj *sourcev1.GitRepository, includes *artifactSet) bool {
952+
if !pointer.StringEqual(obj.Spec.Ignore, obj.Status.ObservedIgnore) {
953+
return true
954+
}
955+
if obj.Spec.RecurseSubmodules != obj.Status.ObservedRecurseSubmodules {
956+
return true
957+
}
958+
if len(obj.Spec.Include) != len(obj.Status.ObservedInclude) {
959+
return true
960+
}
961+
962+
// Convert artifactSet to index addressable artifacts and ensure that it and
963+
// included artifacts include all the include from the spec.
964+
artifacts := []*sourcev1.Artifact(*includes)
965+
if len(obj.Spec.Include) != len(artifacts) {
966+
return true
967+
}
968+
if len(obj.Spec.Include) != len(obj.Status.IncludedArtifacts) {
969+
return true
970+
}
971+
972+
// The order of spec.include, status.IncludeArtifacts and
973+
// status.observedInclude are the same. Compare the values by index.
974+
for index, incl := range obj.Spec.Include {
975+
observedIncl := obj.Status.ObservedInclude[index]
976+
observedInclArtifact := obj.Status.IncludedArtifacts[index]
977+
currentIncl := artifacts[index]
978+
979+
// Check if the include are the same in spec and status.
980+
if !gitRepositoryIncludeEqual(incl, observedIncl) {
981+
return true
982+
}
983+
984+
// Check if the included repositories are still the same.
985+
if observedInclArtifact.Revision != currentIncl.Revision {
986+
return true
987+
}
988+
if observedInclArtifact.Checksum != currentIncl.Checksum {
989+
return true
990+
}
991+
}
992+
return false
993+
}
994+
995+
// Returns true if both GitRepositoryIncludes are equal.
996+
func gitRepositoryIncludeEqual(a, b sourcev1.GitRepositoryInclude) bool {
997+
if a.GitRepositoryRef != b.GitRepositoryRef {
998+
return false
999+
}
1000+
if a.FromPath != b.FromPath {
1001+
return false
1002+
}
1003+
if a.ToPath != b.ToPath {
1004+
return false
1005+
}
1006+
return true
1007+
}

0 commit comments

Comments
 (0)