Sparse Checkout Directories in GitRepositories.
- Add `.spec.sparseCheckout` and `.status.observedSparseCheckout` fields to `GitRepository`. - Add controller support to send the sparse checkout directories to go-git via pkg methods. - Use `.status/observedSparseCheckout` to detect drift in configuration. - Trim leading "./" in directory paths. - Validate spec configuration by checking directories specified in spec exist in the cloned repository after successful checkout - Add tests for testing the observed sparse checkout behavior. - Add docs describing the new fields. Signed-off-by: Dipti Pai <diptipai89@outlook.com>
This commit is contained in:
parent
034bc4962d
commit
61f9eb28fc
|
@ -148,6 +148,12 @@ type GitRepositorySpec struct {
|
||||||
// should be included in the Artifact produced for this GitRepository.
|
// should be included in the Artifact produced for this GitRepository.
|
||||||
// +optional
|
// +optional
|
||||||
Include []GitRepositoryInclude `json:"include,omitempty"`
|
Include []GitRepositoryInclude `json:"include,omitempty"`
|
||||||
|
|
||||||
|
// SparseCheckout specifies a list of directories to checkout when cloning
|
||||||
|
// the repository. If specified, only these directories are included in the
|
||||||
|
// Artifact produced for this GitRepository.
|
||||||
|
// +optional
|
||||||
|
SparseCheckout []string `json:"sparseCheckout,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitRepositoryInclude specifies a local reference to a GitRepository which
|
// GitRepositoryInclude specifies a local reference to a GitRepository which
|
||||||
|
@ -266,6 +272,11 @@ type GitRepositoryStatus struct {
|
||||||
// +optional
|
// +optional
|
||||||
ObservedInclude []GitRepositoryInclude `json:"observedInclude,omitempty"`
|
ObservedInclude []GitRepositoryInclude `json:"observedInclude,omitempty"`
|
||||||
|
|
||||||
|
// ObservedSparseCheckout is the observed list of directories used to
|
||||||
|
// produce the current Artifact.
|
||||||
|
// +optional
|
||||||
|
ObservedSparseCheckout []string `json:"observedSparseCheckout,omitempty"`
|
||||||
|
|
||||||
// SourceVerificationMode is the last used verification mode indicating
|
// SourceVerificationMode is the last used verification mode indicating
|
||||||
// which Git object(s) have been verified.
|
// which Git object(s) have been verified.
|
||||||
// +optional
|
// +optional
|
||||||
|
|
|
@ -347,6 +347,11 @@ func (in *GitRepositorySpec) DeepCopyInto(out *GitRepositorySpec) {
|
||||||
*out = make([]GitRepositoryInclude, len(*in))
|
*out = make([]GitRepositoryInclude, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.SparseCheckout != nil {
|
||||||
|
in, out := &in.SparseCheckout, &out.SparseCheckout
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositorySpec.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositorySpec.
|
||||||
|
@ -395,6 +400,11 @@ func (in *GitRepositoryStatus) DeepCopyInto(out *GitRepositoryStatus) {
|
||||||
*out = make([]GitRepositoryInclude, len(*in))
|
*out = make([]GitRepositoryInclude, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.ObservedSparseCheckout != nil {
|
||||||
|
in, out := &in.ObservedSparseCheckout, &out.ObservedSparseCheckout
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
if in.SourceVerificationMode != nil {
|
if in.SourceVerificationMode != nil {
|
||||||
in, out := &in.SourceVerificationMode, &out.SourceVerificationMode
|
in, out := &in.SourceVerificationMode, &out.SourceVerificationMode
|
||||||
*out = new(GitVerificationMode)
|
*out = new(GitVerificationMode)
|
||||||
|
|
|
@ -174,6 +174,14 @@ spec:
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
|
sparseCheckout:
|
||||||
|
description: |-
|
||||||
|
SparseCheckout specifies a list of directories to checkout when cloning
|
||||||
|
the repository. If specified, only these directories are included in the
|
||||||
|
Artifact produced for this GitRepository.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
suspend:
|
suspend:
|
||||||
description: |-
|
description: |-
|
||||||
Suspend tells the controller to suspend the reconciliation of this
|
Suspend tells the controller to suspend the reconciliation of this
|
||||||
|
@ -443,6 +451,13 @@ spec:
|
||||||
ObservedRecurseSubmodules is the observed resource submodules
|
ObservedRecurseSubmodules is the observed resource submodules
|
||||||
configuration used to produce the current Artifact.
|
configuration used to produce the current Artifact.
|
||||||
type: boolean
|
type: boolean
|
||||||
|
observedSparseCheckout:
|
||||||
|
description: |-
|
||||||
|
ObservedSparseCheckout is the observed list of directories used to
|
||||||
|
produce the current Artifact.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
sourceVerificationMode:
|
sourceVerificationMode:
|
||||||
description: |-
|
description: |-
|
||||||
SourceVerificationMode is the last used verification mode indicating
|
SourceVerificationMode is the last used verification mode indicating
|
||||||
|
|
|
@ -523,6 +523,20 @@ the GitRepository as cloned from the URL, using their default settings.</p>
|
||||||
should be included in the Artifact produced for this GitRepository.</p>
|
should be included in the Artifact produced for this GitRepository.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>sparseCheckout</code><br>
|
||||||
|
<em>
|
||||||
|
[]string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>SparseCheckout specifies a list of directories to checkout when cloning
|
||||||
|
the repository. If specified, only these directories are included in the
|
||||||
|
Artifact produced for this GitRepository.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -1863,6 +1877,20 @@ the GitRepository as cloned from the URL, using their default settings.</p>
|
||||||
should be included in the Artifact produced for this GitRepository.</p>
|
should be included in the Artifact produced for this GitRepository.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>sparseCheckout</code><br>
|
||||||
|
<em>
|
||||||
|
[]string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>SparseCheckout specifies a list of directories to checkout when cloning
|
||||||
|
the repository. If specified, only these directories are included in the
|
||||||
|
Artifact produced for this GitRepository.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1983,6 +2011,19 @@ produce the current Artifact.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
<code>observedSparseCheckout</code><br>
|
||||||
|
<em>
|
||||||
|
[]string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>ObservedSparseCheckout is the observed list of directories used to
|
||||||
|
produce the current Artifact.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<code>sourceVerificationMode</code><br>
|
<code>sourceVerificationMode</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="#source.toolkit.fluxcd.io/v1.GitVerificationMode">
|
<a href="#source.toolkit.fluxcd.io/v1.GitVerificationMode">
|
||||||
|
|
|
@ -615,6 +615,28 @@ list](#default-exclusions), and may overrule the [`.sourceignore` file
|
||||||
exclusions](#sourceignore-file). See [excluding files](#excluding-files)
|
exclusions](#sourceignore-file). See [excluding files](#excluding-files)
|
||||||
for more information.
|
for more information.
|
||||||
|
|
||||||
|
### Sparse checkout
|
||||||
|
|
||||||
|
`.spec.sparseCheckout` is an optional field to specify list of directories to
|
||||||
|
checkout when cloning the repository. If specified, only the specified directory
|
||||||
|
contents will be present in the artifact produced for this repository.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: source.toolkit.fluxcd.io/v1
|
||||||
|
kind: GitRepository
|
||||||
|
metadata:
|
||||||
|
name: podinfo
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
url: https://github.com/stefanprodan/podinfo
|
||||||
|
ref:
|
||||||
|
branch: master
|
||||||
|
sparseCheckout:
|
||||||
|
- charts
|
||||||
|
- kustomize
|
||||||
|
```
|
||||||
|
|
||||||
### Suspend
|
### Suspend
|
||||||
|
|
||||||
`.spec.suspend` is an optional field to suspend the reconciliation of a
|
`.spec.suspend` is an optional field to suspend the reconciliation of a
|
||||||
|
@ -1157,6 +1179,27 @@ status:
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Observed Sparse Checkout
|
||||||
|
|
||||||
|
The source-controller reports observed sparse checkout in the GitRepository's
|
||||||
|
`.status.observedSparseCheckout`. The observed sparse checkout is the latest
|
||||||
|
`.spec.sparseCheckout` value which resulted in a [ready
|
||||||
|
state](#ready-gitrepository), or stalled due to error it can not recover from
|
||||||
|
without human intervention. The value is the same as the [sparseCheckout in
|
||||||
|
spec](#sparse-checkout). It indicates the sparse checkout configuration used in
|
||||||
|
building the current artifact in storage. It is also used by the controller to
|
||||||
|
determine if an artifact needs to be rebuilt.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```yaml
|
||||||
|
status:
|
||||||
|
...
|
||||||
|
observedSparseCheckout:
|
||||||
|
- charts
|
||||||
|
- kustomize
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
### Source Verification Mode
|
### Source Verification Mode
|
||||||
|
|
||||||
The source-controller reports the Git object(s) it verified in the Git
|
The source-controller reports the Git object(s) it verified in the Git
|
||||||
|
|
|
@ -590,6 +590,16 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
|
||||||
ctrl.LoggerFrom(ctx).V(logger.DebugLevel).Info("git repository checked out", "url", obj.Spec.URL, "revision", commitReference(obj, commit))
|
ctrl.LoggerFrom(ctx).V(logger.DebugLevel).Info("git repository checked out", "url", obj.Spec.URL, "revision", commitReference(obj, commit))
|
||||||
conditions.Delete(obj, sourcev1.FetchFailedCondition)
|
conditions.Delete(obj, sourcev1.FetchFailedCondition)
|
||||||
|
|
||||||
|
// Validate sparse checkout paths after successful checkout.
|
||||||
|
if err := r.validateSparseCheckoutPaths(ctx, obj, dir); err != nil {
|
||||||
|
e := serror.NewStalling(
|
||||||
|
fmt.Errorf("failed to sparse checkout directories : %w", err),
|
||||||
|
sourcev1.GitOperationFailedReason,
|
||||||
|
)
|
||||||
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||||
|
return sreconcile.ResultEmpty, e
|
||||||
|
}
|
||||||
|
|
||||||
// Verify commit signature
|
// Verify commit signature
|
||||||
if result, err := r.verifySignature(ctx, obj, *commit); err != nil || result == sreconcile.ResultEmpty {
|
if result, err := r.verifySignature(ctx, obj, *commit); err != nil || result == sreconcile.ResultEmpty {
|
||||||
return result, err
|
return result, err
|
||||||
|
@ -812,6 +822,7 @@ func (r *GitRepositoryReconciler) reconcileArtifact(ctx context.Context, sp *pat
|
||||||
obj.Status.ObservedIgnore = obj.Spec.Ignore
|
obj.Status.ObservedIgnore = obj.Spec.Ignore
|
||||||
obj.Status.ObservedRecurseSubmodules = obj.Spec.RecurseSubmodules
|
obj.Status.ObservedRecurseSubmodules = obj.Spec.RecurseSubmodules
|
||||||
obj.Status.ObservedInclude = obj.Spec.Include
|
obj.Status.ObservedInclude = obj.Spec.Include
|
||||||
|
obj.Status.ObservedSparseCheckout = obj.Spec.SparseCheckout
|
||||||
|
|
||||||
// Remove the deprecated symlink.
|
// Remove the deprecated symlink.
|
||||||
// TODO(hidde): remove 2 minor versions from introduction of v1.
|
// TODO(hidde): remove 2 minor versions from introduction of v1.
|
||||||
|
@ -884,6 +895,7 @@ func (r *GitRepositoryReconciler) reconcileInclude(ctx context.Context, sp *patc
|
||||||
// performs a git checkout.
|
// performs a git checkout.
|
||||||
func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context, obj *sourcev1.GitRepository,
|
func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context, obj *sourcev1.GitRepository,
|
||||||
authOpts *git.AuthOptions, proxyOpts *transport.ProxyOptions, dir string, optimized bool) (*git.Commit, error) {
|
authOpts *git.AuthOptions, proxyOpts *transport.ProxyOptions, dir string, optimized bool) (*git.Commit, error) {
|
||||||
|
|
||||||
// Configure checkout strategy.
|
// Configure checkout strategy.
|
||||||
cloneOpts := repository.CloneConfig{
|
cloneOpts := repository.CloneConfig{
|
||||||
RecurseSubmodules: obj.Spec.RecurseSubmodules,
|
RecurseSubmodules: obj.Spec.RecurseSubmodules,
|
||||||
|
@ -896,7 +908,14 @@ func (r *GitRepositoryReconciler) gitCheckout(ctx context.Context, obj *sourcev1
|
||||||
cloneOpts.SemVer = ref.SemVer
|
cloneOpts.SemVer = ref.SemVer
|
||||||
cloneOpts.RefName = ref.Name
|
cloneOpts.RefName = ref.Name
|
||||||
}
|
}
|
||||||
|
if obj.Spec.SparseCheckout != nil {
|
||||||
|
// Trim any leading "./" in the directory paths since underlying go-git API does not honor them.
|
||||||
|
sparseCheckoutDirs := make([]string, len(obj.Spec.SparseCheckout))
|
||||||
|
for i, path := range obj.Spec.SparseCheckout {
|
||||||
|
sparseCheckoutDirs[i] = strings.TrimPrefix(path, "./")
|
||||||
|
}
|
||||||
|
cloneOpts.SparseCheckoutDirectories = sparseCheckoutDirs
|
||||||
|
}
|
||||||
// Only if the object has an existing artifact in storage, attempt to
|
// Only if the object has an existing artifact in storage, attempt to
|
||||||
// short-circuit clone operation. reconcileStorage has already verified
|
// short-circuit clone operation. reconcileStorage has already verified
|
||||||
// that the artifact exists.
|
// that the artifact exists.
|
||||||
|
@ -1172,6 +1191,14 @@ func gitContentConfigChanged(obj *sourcev1.GitRepository, includes *artifactSet)
|
||||||
if requiresVerification(obj) {
|
if requiresVerification(obj) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if len(obj.Spec.SparseCheckout) != len(obj.Status.ObservedSparseCheckout) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for index, dir := range obj.Spec.SparseCheckout {
|
||||||
|
if dir != obj.Status.ObservedSparseCheckout[index] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Convert artifactSet to index addressable artifacts and ensure that it and
|
// Convert artifactSet to index addressable artifacts and ensure that it and
|
||||||
// the included artifacts include all the include from the spec.
|
// the included artifacts include all the include from the spec.
|
||||||
|
@ -1206,6 +1233,19 @@ func gitContentConfigChanged(obj *sourcev1.GitRepository, includes *artifactSet)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateSparseCheckoutPaths checks if the sparse checkout paths exist in the cloned repository.
|
||||||
|
func (r *GitRepositoryReconciler) validateSparseCheckoutPaths(ctx context.Context, obj *sourcev1.GitRepository, dir string) error {
|
||||||
|
if obj.Spec.SparseCheckout != nil {
|
||||||
|
for _, path := range obj.Spec.SparseCheckout {
|
||||||
|
fullPath := filepath.Join(dir, path)
|
||||||
|
if _, err := os.Lstat(fullPath); err != nil {
|
||||||
|
return fmt.Errorf("sparse checkout dir '%s' does not exist in repository: %w", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Returns true if both GitRepositoryIncludes are equal.
|
// Returns true if both GitRepositoryIncludes are equal.
|
||||||
func gitRepositoryIncludeEqual(a, b sourcev1.GitRepositoryInclude) bool {
|
func gitRepositoryIncludeEqual(a, b sourcev1.GitRepositoryInclude) bool {
|
||||||
if a.GitRepositoryRef != b.GitRepositoryRef {
|
if a.GitRepositoryRef != b.GitRepositoryRef {
|
||||||
|
|
|
@ -3130,6 +3130,38 @@ func TestGitContentConfigChanged(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: false,
|
want: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "unobserved sparse checkout",
|
||||||
|
obj: sourcev1.GitRepository{
|
||||||
|
Spec: sourcev1.GitRepositorySpec{SparseCheckout: []string{"a/b/c", "x/y/z"}},
|
||||||
|
Status: sourcev1.GitRepositoryStatus{ObservedSparseCheckout: []string{"a/b/c"}},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unobserved case sensitive sparse checkout",
|
||||||
|
obj: sourcev1.GitRepository{
|
||||||
|
Spec: sourcev1.GitRepositorySpec{SparseCheckout: []string{"a/b/c", "x/y/Z"}},
|
||||||
|
Status: sourcev1.GitRepositoryStatus{ObservedSparseCheckout: []string{"a/b/c", "x/y/z"}},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "observed sparse checkout",
|
||||||
|
obj: sourcev1.GitRepository{
|
||||||
|
Spec: sourcev1.GitRepositorySpec{SparseCheckout: []string{"a/b/c", "x/y/z"}},
|
||||||
|
Status: sourcev1.GitRepositoryStatus{ObservedSparseCheckout: []string{"a/b/c", "x/y/z"}},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "observed sparse checkout with leading slash",
|
||||||
|
obj: sourcev1.GitRepository{
|
||||||
|
Spec: sourcev1.GitRepositorySpec{SparseCheckout: []string{"./a/b/c", "./x/y/z"}},
|
||||||
|
Status: sourcev1.GitRepositoryStatus{ObservedSparseCheckout: []string{"./a/b/c", "./x/y/z"}},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "unobserved include",
|
name: "unobserved include",
|
||||||
obj: sourcev1.GitRepository{
|
obj: sourcev1.GitRepository{
|
||||||
|
|
Loading…
Reference in New Issue