Add GitRepositoryRef type

- add commit ref field
- implement commit checkout
This commit is contained in:
stefanprodan 2020-04-11 11:21:12 +03:00
parent 686fdd49dc
commit e2d28296e1
5 changed files with 143 additions and 66 deletions

View File

@ -31,7 +31,14 @@ type GitRepositorySpec struct {
// +required
Interval metav1.Duration `json:"interval"`
// The git branch to checkout, defaults to ('master').
// The git reference to checkout and monitor for changes, defaults to master branch.
// +optional
Reference *GitRepositoryRef `json:"ref,omitempty"`
}
// GitRepositoryRef defines the git ref used for pull and checkout operations
type GitRepositoryRef struct {
// The git branch to checkout, defaults to master.
// +optional
Branch string `json:"branch"`
@ -42,6 +49,10 @@ type GitRepositorySpec struct {
// The git tag semver expression, takes precedence over tag.
// +optional
SemVer string `json:"semver"`
// The git commit sha to checkout, if specified tag filters will be ignored.
// +optional
Commit string `json:"commit"`
}
// GitRepositoryStatus defines the observed state of GitRepository

View File

@ -29,7 +29,7 @@ func (in *GitRepository) DeepCopyInto(out *GitRepository) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
@ -83,10 +83,30 @@ func (in *GitRepositoryList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GitRepositoryRef) DeepCopyInto(out *GitRepositoryRef) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositoryRef.
func (in *GitRepositoryRef) DeepCopy() *GitRepositoryRef {
if in == nil {
return nil
}
out := new(GitRepositoryRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *GitRepositorySpec) DeepCopyInto(out *GitRepositorySpec) {
*out = *in
out.Interval = in.Interval
if in.Reference != nil {
in, out := &in.Reference, &out.Reference
*out = new(GitRepositoryRef)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositorySpec.

View File

@ -49,18 +49,28 @@ spec:
spec:
description: GitRepositorySpec defines the desired state of GitRepository
properties:
branch:
description: The git branch to checkout, defaults to ('master').
type: string
interval:
description: The interval at which to check for repository updates.
type: string
semver:
description: The git tag semver expression, takes precedence over tag.
type: string
tag:
description: The git tag to checkout, takes precedence over branch.
type: string
ref:
description: The git reference to checkout and monitor for changes,
defaults to master branch.
properties:
branch:
description: The git branch to checkout, defaults to master.
type: string
commit:
description: The git commit sha to checkout, if specified tag filters
will be ignored.
type: string
semver:
description: The git tag semver expression, takes precedence over
tag.
type: string
tag:
description: The git tag to checkout, takes precedence over branch.
type: string
type: object
url:
description: The repository URL, can be a HTTP or SSH address.
pattern: ^(http|https|ssh)://

View File

@ -7,5 +7,5 @@ metadata:
spec:
interval: 1m
url: https://github.com/stefanprodan/podinfo
branch: master
tag: "3.2.2"
ref:
branch: master

View File

@ -126,13 +126,28 @@ func (r *GitRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error {
}
func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourcev1.SourceCondition, string, error) {
// set defaults: master branch, no tags fetching, max two commits
branch := "master"
tagMode := git.NoTags
depth := 2
// determine ref
refName := plumbing.NewBranchReferenceName("master")
if repository.Spec.Branch != "" {
refName = plumbing.NewBranchReferenceName(repository.Spec.Branch)
}
if repository.Spec.Tag != "" {
refName = plumbing.NewTagReferenceName(repository.Spec.Tag)
refName := plumbing.NewBranchReferenceName(branch)
if repository.Spec.Reference != nil {
if repository.Spec.Reference.Branch != "" {
branch = repository.Spec.Reference.Branch
refName = plumbing.NewBranchReferenceName(branch)
}
if repository.Spec.Reference.Commit != "" {
depth = 0
} else {
if repository.Spec.Reference.Tag != "" {
refName = plumbing.NewTagReferenceName(repository.Spec.Reference.Tag)
}
if repository.Spec.Reference.SemVer != "" {
tagMode = git.AllTags
}
}
}
// create tmp dir
@ -145,53 +160,25 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
// clone to tmp
repo, err := git.PlainClone(dir, false, &git.CloneOptions{
URL: repository.Spec.URL,
Depth: 2,
ReferenceName: refName,
SingleBranch: true,
Tags: git.AllTags,
URL: repository.Spec.URL,
Auth: nil,
RemoteName: "origin",
ReferenceName: refName,
SingleBranch: true,
NoCheckout: false,
Depth: depth,
RecurseSubmodules: 0,
Progress: nil,
Tags: tagMode,
})
if err != nil {
err = fmt.Errorf("git clone error %w", err)
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
}
// checkout tag based on semver expression
if repository.Spec.SemVer != "" {
rng, err := semver.ParseRange(repository.Spec.SemVer)
if err != nil {
err = fmt.Errorf("semver parse range error %w", err)
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
}
repoTags, err := repo.Tags()
if err != nil {
err = fmt.Errorf("git list tags error %w", err)
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
}
tags := make(map[string]string)
_ = repoTags.ForEach(func(t *plumbing.Reference) error {
tags[t.Name().Short()] = t.Strings()[1]
return nil
})
svTags := make(map[string]string)
svers := []semver.Version{}
for tag, _ := range tags {
v, _ := semver.ParseTolerant(tag)
if rng(v) {
svers = append(svers, v)
svTags[v.String()] = tag
}
}
if len(svers) > 0 {
semver.Sort(svers)
v := svers[len(svers)-1]
t := svTags[v.String()]
commit := tags[t]
// checkout commit or tag
if repository.Spec.Reference != nil {
if commit := repository.Spec.Reference.Commit; commit != "" {
w, err := repo.Worktree()
if err != nil {
err = fmt.Errorf("git worktree error %w", err)
@ -199,18 +186,67 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
}
err = w.Checkout(&git.CheckoutOptions{
Hash: plumbing.NewHash(commit),
Hash: plumbing.NewHash(commit),
Force: true,
})
if err != nil {
err = fmt.Errorf("git checkout error %w", err)
err = fmt.Errorf("git checkout %s for %s error %w", commit, branch, err)
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
}
} else if exp := repository.Spec.Reference.SemVer; exp != "" {
rng, err := semver.ParseRange(exp)
if err != nil {
err = fmt.Errorf("semver parse range error %w", err)
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
}
repoTags, err := repo.Tags()
if err != nil {
err = fmt.Errorf("git list tags error %w", err)
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
}
tags := make(map[string]string)
_ = repoTags.ForEach(func(t *plumbing.Reference) error {
tags[t.Name().Short()] = t.Strings()[1]
return nil
})
svTags := make(map[string]string)
svers := []semver.Version{}
for tag, _ := range tags {
v, _ := semver.ParseTolerant(tag)
if rng(v) {
svers = append(svers, v)
svTags[v.String()] = tag
}
}
if len(svers) > 0 {
semver.Sort(svers)
v := svers[len(svers)-1]
t := svTags[v.String()]
commit := tags[t]
w, err := repo.Worktree()
if err != nil {
err = fmt.Errorf("git worktree error %w", err)
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
}
err = w.Checkout(&git.CheckoutOptions{
Hash: plumbing.NewHash(commit),
})
if err != nil {
err = fmt.Errorf("git checkout error %w", err)
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
}
} else {
err = fmt.Errorf("no match found for semver %s", repository.Spec.Reference.SemVer)
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
}
} else {
err = fmt.Errorf("no match found for semver %s", repository.Spec.SemVer)
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
}
}
// read commit hash
ref, err := repo.Head()
if err != nil {