Skip to content

Commit e996848

Browse files
committed
GitRepo: Add observed content config in status
Replace content config checksum with explicit artifact content 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 278a223 commit e996848

File tree

7 files changed

+639
-106
lines changed

7 files changed

+639
-106
lines changed

api/v1beta2/gitrepository_types.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,27 @@ type GitRepositoryStatus struct {
224224
// be used to determine if the content of the included repository has
225225
// changed.
226226
// It has the format of `<algo>:<checksum>`, for example: `sha256:<checksum>`.
227+
//
228+
// Deprecated: Replaced with explicit fields for observed artifact content
229+
// config in the status.
227230
// +optional
228231
ContentConfigChecksum string `json:"contentConfigChecksum,omitempty"`
229232

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

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: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -658,13 +658,14 @@ spec:
658658
type: object
659659
type: array
660660
contentConfigChecksum:
661-
description: 'ContentConfigChecksum is a checksum of all the configurations
661+
description: "ContentConfigChecksum is a checksum of all the configurations
662662
related to the content of the source artifact: - .spec.ignore -
663663
.spec.recurseSubmodules - .spec.included and the checksum of the
664664
included artifacts observed in .status.observedGeneration version
665665
of the object. This can be used to determine if the content of the
666666
included repository has changed. It has the format of `<algo>:<checksum>`,
667-
for example: `sha256:<checksum>`.'
667+
for example: `sha256:<checksum>`. \n Deprecated: Replaced with explicit
668+
fields for observed artifact content config in the status."
668669
type: string
669670
includedArtifacts:
670671
description: IncludedArtifacts contains a list of the last successfully
@@ -723,6 +724,44 @@ spec:
723724
the GitRepository object.
724725
format: int64
725726
type: integer
727+
observedIgnore:
728+
description: ObservedIgnore is the observed exclusion patterns used
729+
for constructing the source artifact.
730+
type: string
731+
observedInclude:
732+
description: ObservedInclude is the observed list of GitRepository
733+
resources used to to produce the current Artifact.
734+
items:
735+
description: GitRepositoryInclude specifies a local reference to
736+
a GitRepository which Artifact (sub-)contents must be included,
737+
and where they should be placed.
738+
properties:
739+
fromPath:
740+
description: FromPath specifies the path to copy contents from,
741+
defaults to the root of the Artifact.
742+
type: string
743+
repository:
744+
description: GitRepositoryRef specifies the GitRepository which
745+
Artifact contents must be included.
746+
properties:
747+
name:
748+
description: Name of the referent.
749+
type: string
750+
required:
751+
- name
752+
type: object
753+
toPath:
754+
description: ToPath specifies the path to copy contents to,
755+
defaults to the name of the GitRepositoryRef.
756+
type: string
757+
required:
758+
- repository
759+
type: object
760+
type: array
761+
observedRecurseSubmodules:
762+
description: ObservedRecurseSubmodules is the observed resource submodules
763+
configuration used to produce the current Artifact.
764+
type: boolean
726765
url:
727766
description: URL is the dynamic fetch link for the latest Artifact.
728767
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 content config contributing to the artifact has changed.
510+
if !gitContentConfigChanged(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 artifact
562+
// content 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+
!gitContentConfigChanged(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+
!gitContentConfigChanged(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+
// gitContentConfigChanged evaluates the current spec with the observations of
949+
// the artifact in the status to determine if artifact content configuration has
950+
// changed and requires rebuilding the artifact.
951+
func gitContentConfigChanged(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+
// the 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)