Add artifact to Git and Helm repos status
- create index.yaml symlink for Helm repos - set symlink URL in status
This commit is contained in:
parent
461c015273
commit
2bbcd91544
|
@ -42,3 +42,61 @@ func NotReadyCondition(reason, message string) sourcev1.SourceCondition {
|
||||||
Message: message,
|
Message: message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReadyGitRepository(repository sourcev1.GitRepository, artifact sourcev1.Artifact, url, reason, message string) sourcev1.GitRepository {
|
||||||
|
repository.Status.Conditions = []sourcev1.SourceCondition{ReadyCondition(reason, message)}
|
||||||
|
repository.Status.URL = url
|
||||||
|
|
||||||
|
if repository.Status.Artifact != nil {
|
||||||
|
if repository.Status.Artifact.Path != artifact.Path {
|
||||||
|
repository.Status.Artifact = &artifact
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repository.Status.Artifact = &artifact
|
||||||
|
}
|
||||||
|
|
||||||
|
return repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NotReadyGitRepository(repository sourcev1.GitRepository, reason, message string) sourcev1.GitRepository {
|
||||||
|
repository.Status.Conditions = []sourcev1.SourceCondition{NotReadyCondition(reason, message)}
|
||||||
|
return repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func GitRepositoryReadyMessage(repository sourcev1.GitRepository) string {
|
||||||
|
for _, condition := range repository.Status.Conditions {
|
||||||
|
if condition.Type == sourcev1.ReadyCondition {
|
||||||
|
return condition.Message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadyHelmRepository(repository sourcev1.HelmRepository, artifact sourcev1.Artifact, url, reason, message string) sourcev1.HelmRepository {
|
||||||
|
repository.Status.Conditions = []sourcev1.SourceCondition{ReadyCondition(reason, message)}
|
||||||
|
repository.Status.URL = url
|
||||||
|
|
||||||
|
if repository.Status.Artifact != nil {
|
||||||
|
if repository.Status.Artifact.Path != artifact.Path {
|
||||||
|
repository.Status.Artifact = &artifact
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repository.Status.Artifact = &artifact
|
||||||
|
}
|
||||||
|
|
||||||
|
return repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NotReadyHelmRepository(repository sourcev1.HelmRepository, reason, message string) sourcev1.HelmRepository {
|
||||||
|
repository.Status.Conditions = []sourcev1.SourceCondition{NotReadyCondition(reason, message)}
|
||||||
|
return repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func HelmRepositoryReadyMessage(repository sourcev1.HelmRepository) string {
|
||||||
|
for _, condition := range repository.Status.Conditions {
|
||||||
|
if condition.Type == sourcev1.ReadyCondition {
|
||||||
|
return condition.Message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
|
@ -83,28 +83,19 @@ func (r *GitRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
|
||||||
r.gc(repo)
|
r.gc(repo)
|
||||||
|
|
||||||
// try git clone
|
// try git clone
|
||||||
readyCondition, artifacts, err := r.sync(repo)
|
syncedRepo, err := r.sync(*repo.DeepCopy())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Repository sync failed", "error", err.Error())
|
log.Info("Repository sync failed", "error", err.Error())
|
||||||
} else {
|
|
||||||
// update artifacts if commit hash changed
|
|
||||||
if repo.Status.Artifact != artifacts {
|
|
||||||
timeNew := metav1.Now()
|
|
||||||
repo.Status.LastUpdateTime = &timeNew
|
|
||||||
repo.Status.Artifact = artifacts
|
|
||||||
}
|
|
||||||
log.Info("Repository sync succeeded", "msg", readyCondition.Message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update status
|
// update status
|
||||||
readyCondition.LastTransitionTime = metav1.Now()
|
if err := r.Status().Update(ctx, &syncedRepo); err != nil {
|
||||||
repo.Status.Conditions = []sourcev1.SourceCondition{readyCondition}
|
|
||||||
|
|
||||||
if err := r.Status().Update(ctx, &repo); err != nil {
|
|
||||||
log.Error(err, "unable to update GitRepository status")
|
log.Error(err, "unable to update GitRepository status")
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("Repository sync succeeded", "msg", GitRepositoryReadyMessage(syncedRepo))
|
||||||
|
|
||||||
// requeue repository
|
// requeue repository
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
@ -116,7 +107,7 @@ func (r *GitRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
WithEventFilter(predicate.Funcs{
|
WithEventFilter(predicate.Funcs{
|
||||||
DeleteFunc: func(e event.DeleteEvent) bool {
|
DeleteFunc: func(e event.DeleteEvent) bool {
|
||||||
// delete artifacts
|
// delete artifacts
|
||||||
artifact := r.Storage.ArtifactFor(r.Kind, e.Meta, "dummy")
|
artifact := r.Storage.ArtifactFor(r.Kind, e.Meta, "dummy", "")
|
||||||
if err := r.Storage.RemoveAll(artifact); err != nil {
|
if err := r.Storage.RemoveAll(artifact); err != nil {
|
||||||
r.Log.Error(err, "unable to delete artifacts",
|
r.Log.Error(err, "unable to delete artifacts",
|
||||||
r.Kind, fmt.Sprintf("%s/%s", e.Meta.GetNamespace(), e.Meta.GetName()))
|
r.Kind, fmt.Sprintf("%s/%s", e.Meta.GetNamespace(), e.Meta.GetName()))
|
||||||
|
@ -130,9 +121,10 @@ func (r *GitRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
Complete(r)
|
Complete(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourcev1.SourceCondition, string, error) {
|
func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourcev1.GitRepository, error) {
|
||||||
// set defaults: master branch, no tags fetching, max two commits
|
// set defaults: master branch, no tags fetching, max two commits
|
||||||
branch := "master"
|
branch := "master"
|
||||||
|
revision := ""
|
||||||
tagMode := git.NoTags
|
tagMode := git.NoTags
|
||||||
depth := 2
|
depth := 2
|
||||||
|
|
||||||
|
@ -159,21 +151,21 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
|
||||||
tmpSSH, err := ioutil.TempDir("", repository.Name)
|
tmpSSH, err := ioutil.TempDir("", repository.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("tmp dir error: %w", err)
|
err = fmt.Errorf("tmp dir error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.StorageOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpSSH)
|
defer os.RemoveAll(tmpSSH)
|
||||||
|
|
||||||
auth, err := r.auth(repository, tmpSSH)
|
auth, err := r.auth(repository, tmpSSH)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("auth error: %w", err)
|
err = fmt.Errorf("auth error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.AuthenticationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create tmp dir for the Git clone
|
// create tmp dir for the Git clone
|
||||||
tmpGit, err := ioutil.TempDir("", repository.Name)
|
tmpGit, err := ioutil.TempDir("", repository.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("tmp dir error: %w", err)
|
err = fmt.Errorf("tmp dir error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.StorageOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpGit)
|
defer os.RemoveAll(tmpGit)
|
||||||
|
|
||||||
|
@ -192,7 +184,7 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("git clone error: %w", err)
|
err = fmt.Errorf("git clone error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkout commit or tag
|
// checkout commit or tag
|
||||||
|
@ -201,7 +193,7 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
|
||||||
w, err := repo.Worktree()
|
w, err := repo.Worktree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("git worktree error: %w", err)
|
err = fmt.Errorf("git worktree error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = w.Checkout(&git.CheckoutOptions{
|
err = w.Checkout(&git.CheckoutOptions{
|
||||||
|
@ -210,19 +202,19 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("git checkout %s for %s error: %w", commit, branch, err)
|
err = fmt.Errorf("git checkout %s for %s error: %w", commit, branch, err)
|
||||||
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
} else if exp := repository.Spec.Reference.SemVer; exp != "" {
|
} else if exp := repository.Spec.Reference.SemVer; exp != "" {
|
||||||
rng, err := semver.ParseRange(exp)
|
rng, err := semver.ParseRange(exp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("semver parse range error: %w", err)
|
err = fmt.Errorf("semver parse range error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
repoTags, err := repo.Tags()
|
repoTags, err := repo.Tags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("git list tags error: %w", err)
|
err = fmt.Errorf("git list tags error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := make(map[string]string)
|
tags := make(map[string]string)
|
||||||
|
@ -246,11 +238,12 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
|
||||||
v := svers[len(svers)-1]
|
v := svers[len(svers)-1]
|
||||||
t := svTags[v.String()]
|
t := svTags[v.String()]
|
||||||
commit := tags[t]
|
commit := tags[t]
|
||||||
|
revision = fmt.Sprintf("%s/%s", t, commit)
|
||||||
|
|
||||||
w, err := repo.Worktree()
|
w, err := repo.Worktree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("git worktree error: %w", err)
|
err = fmt.Errorf("git worktree error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = w.Checkout(&git.CheckoutOptions{
|
err = w.Checkout(&git.CheckoutOptions{
|
||||||
|
@ -258,11 +251,11 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("git checkout error: %w", err)
|
err = fmt.Errorf("git checkout error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("no match found for semver: %s", repository.Spec.Reference.SemVer)
|
err = fmt.Errorf("no match found for semver: %s", repository.Spec.Reference.SemVer)
|
||||||
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,24 +264,28 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
|
||||||
ref, err := repo.Head()
|
ref, err := repo.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("git resolve HEAD error: %w", err)
|
err = fmt.Errorf("git resolve HEAD error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision == "" {
|
||||||
|
revision = fmt.Sprintf("%s/%s", branch, ref.Hash().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(),
|
artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(),
|
||||||
fmt.Sprintf("%s.tar.gz", ref.Hash().String()))
|
fmt.Sprintf("%s.tar.gz", ref.Hash().String()), revision)
|
||||||
|
|
||||||
// create artifact dir
|
// create artifact dir
|
||||||
err = r.Storage.MkdirAll(artifact)
|
err = r.Storage.MkdirAll(artifact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("mkdir dir error: %w", err)
|
err = fmt.Errorf("mkdir dir error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.StorageOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// acquire lock
|
// acquire lock
|
||||||
unlock, err := r.Storage.Lock(artifact)
|
unlock, err := r.Storage.Lock(artifact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("unable to acquire lock: %w", err)
|
err = fmt.Errorf("unable to acquire lock: %w", err)
|
||||||
return NotReadyCondition(sourcev1.StorageOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
|
@ -296,26 +293,24 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
|
||||||
err = r.Storage.Archive(artifact, tmpGit, "")
|
err = r.Storage.Archive(artifact, tmpGit, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("storage archive error: %w", err)
|
err = fmt.Errorf("storage archive error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.StorageOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// update latest symlink
|
// update latest symlink
|
||||||
err = r.Storage.Symlink(artifact, "latest.tar.gz")
|
url, err := r.Storage.Symlink(artifact, "latest.tar.gz")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("storage lock error: %w", err)
|
err = fmt.Errorf("storage lock error: %w", err)
|
||||||
return NotReadyCondition(sourcev1.StorageOperationFailedReason, err.Error()), "", err
|
return NotReadyGitRepository(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
message := fmt.Sprintf("Artifact is available at: %s", artifact.Path)
|
message := fmt.Sprintf("Artifact is available at: %s", artifact.Path)
|
||||||
return ReadyCondition(sourcev1.GitOperationSucceedReason, message), artifact.URL, nil
|
return ReadyGitRepository(repository, artifact, url, sourcev1.GitOperationSucceedReason, message), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *GitRepositoryReconciler) shouldResetStatus(repository sourcev1.GitRepository) (bool, sourcev1.GitRepositoryStatus) {
|
func (r *GitRepositoryReconciler) shouldResetStatus(repository sourcev1.GitRepository) (bool, sourcev1.GitRepositoryStatus) {
|
||||||
resetStatus := false
|
resetStatus := false
|
||||||
if repository.Status.Artifact != "" {
|
if repository.Status.Artifact != nil {
|
||||||
parts := strings.Split(repository.Status.Artifact, "/")
|
if !r.Storage.ArtifactExist(*repository.Status.Artifact) {
|
||||||
artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(), parts[len(parts)-1])
|
|
||||||
if !r.Storage.ArtifactExist(artifact) {
|
|
||||||
resetStatus = true
|
resetStatus = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,10 +333,8 @@ func (r *GitRepositoryReconciler) shouldResetStatus(repository sourcev1.GitRepos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *GitRepositoryReconciler) gc(repository sourcev1.GitRepository) {
|
func (r *GitRepositoryReconciler) gc(repository sourcev1.GitRepository) {
|
||||||
if repository.Status.Artifact != "" {
|
if repository.Status.Artifact != nil {
|
||||||
parts := strings.Split(repository.Status.Artifact, "/")
|
if err := r.Storage.RemoveAllButCurrent(*repository.Status.Artifact); err != nil {
|
||||||
artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(), parts[len(parts)-1])
|
|
||||||
if err := r.Storage.RemoveAllButCurrent(artifact); err != nil {
|
|
||||||
r.Log.Info("Artifacts GC failed", "error", err)
|
r.Log.Info("Artifacts GC failed", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,9 +363,13 @@ func (r *GitRepositoryReconciler) auth(repository sourcev1.GitRepository, tmp st
|
||||||
auth := &http.BasicAuth{}
|
auth := &http.BasicAuth{}
|
||||||
if username, ok := credentials["username"]; ok {
|
if username, ok := credentials["username"]; ok {
|
||||||
auth.Username = string(username)
|
auth.Username = string(username)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("%s secret does not contain username", repository.Spec.SecretRef.Name)
|
||||||
}
|
}
|
||||||
if password, ok := credentials["password"]; ok {
|
if password, ok := credentials["password"]; ok {
|
||||||
auth.Password = string(password)
|
auth.Password = string(password)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("%s secret does not contain password", repository.Spec.SecretRef.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth.Username == "" || auth.Password == "" {
|
if auth.Username == "" || auth.Password == "" {
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
|
@ -57,7 +56,7 @@ func (r *HelmRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
log := r.Log.WithValues("helmrepository", req.NamespacedName)
|
log := r.Log.WithValues(r.Kind, req.NamespacedName)
|
||||||
|
|
||||||
var repository sourcev1.HelmRepository
|
var repository sourcev1.HelmRepository
|
||||||
if err := r.Get(ctx, req.NamespacedName, &repository); err != nil {
|
if err := r.Get(ctx, req.NamespacedName, &repository); err != nil {
|
||||||
|
@ -80,28 +79,19 @@ func (r *HelmRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
|
||||||
r.gc(repository)
|
r.gc(repository)
|
||||||
|
|
||||||
// try to download index
|
// try to download index
|
||||||
readyCondition, artifact, err := r.index(repository)
|
syncedRepo, err := r.sync(*repository.DeepCopy())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Helm repository index failed", "error", err.Error())
|
log.Info("Helm repository index failed", "error", err.Error())
|
||||||
} else {
|
|
||||||
// update artifact if path changed
|
|
||||||
if repository.Status.Artifact != artifact {
|
|
||||||
timeNew := metav1.Now()
|
|
||||||
repository.Status.LastUpdateTime = &timeNew
|
|
||||||
repository.Status.Artifact = artifact
|
|
||||||
}
|
|
||||||
log.Info("Helm repository index succeeded", "msg", readyCondition.Message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update status
|
// update status
|
||||||
readyCondition.LastTransitionTime = metav1.Now()
|
if err := r.Status().Update(ctx, &syncedRepo); err != nil {
|
||||||
repository.Status.Conditions = []sourcev1.SourceCondition{readyCondition}
|
|
||||||
|
|
||||||
if err := r.Status().Update(ctx, &repository); err != nil {
|
|
||||||
log.Error(err, "unable to update HelmRepository status")
|
log.Error(err, "unable to update HelmRepository status")
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("Repository sync succeeded", "msg", HelmRepositoryReadyMessage(syncedRepo))
|
||||||
|
|
||||||
// requeue repository
|
// requeue repository
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
@ -113,7 +103,7 @@ func (r *HelmRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
WithEventFilter(predicate.Funcs{
|
WithEventFilter(predicate.Funcs{
|
||||||
DeleteFunc: func(e event.DeleteEvent) bool {
|
DeleteFunc: func(e event.DeleteEvent) bool {
|
||||||
// delete artifacts
|
// delete artifacts
|
||||||
artifact := r.Storage.ArtifactFor(r.Kind, e.Meta, "dummy")
|
artifact := r.Storage.ArtifactFor(r.Kind, e.Meta, "", "")
|
||||||
if err := r.Storage.RemoveAll(artifact); err != nil {
|
if err := r.Storage.RemoveAll(artifact); err != nil {
|
||||||
r.Log.Error(err, "unable to delete artifacts",
|
r.Log.Error(err, "unable to delete artifacts",
|
||||||
r.Kind, fmt.Sprintf("%s/%s", e.Meta.GetNamespace(), e.Meta.GetName()))
|
r.Kind, fmt.Sprintf("%s/%s", e.Meta.GetNamespace(), e.Meta.GetName()))
|
||||||
|
@ -127,15 +117,15 @@ func (r *HelmRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
Complete(r)
|
Complete(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HelmRepositoryReconciler) index(repository sourcev1.HelmRepository) (sourcev1.SourceCondition, string, error) {
|
func (r *HelmRepositoryReconciler) sync(repository sourcev1.HelmRepository) (sourcev1.HelmRepository, error) {
|
||||||
u, err := url.Parse(repository.Spec.URL)
|
u, err := url.Parse(repository.Spec.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NotReadyCondition(sourcev1.URLInvalidReason, err.Error()), "", err
|
return NotReadyHelmRepository(repository, sourcev1.URLInvalidReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := r.Getters.ByScheme(u.Scheme)
|
c, err := r.Getters.ByScheme(u.Scheme)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NotReadyCondition(sourcev1.URLInvalidReason, err.Error()), "", err
|
return NotReadyHelmRepository(repository, sourcev1.URLInvalidReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.RawPath = path.Join(u.RawPath, "index.yaml")
|
u.RawPath = path.Join(u.RawPath, "index.yaml")
|
||||||
|
@ -145,40 +135,40 @@ func (r *HelmRepositoryReconciler) index(repository sourcev1.HelmRepository) (so
|
||||||
// TODO(hidde): add authentication config
|
// TODO(hidde): add authentication config
|
||||||
res, err := c.Get(indexURL, getter.WithURL(repository.Spec.URL))
|
res, err := c.Get(indexURL, getter.WithURL(repository.Spec.URL))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NotReadyCondition(sourcev1.IndexationFailedReason, err.Error()), "", err
|
return NotReadyHelmRepository(repository, sourcev1.IndexationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(res)
|
data, err := ioutil.ReadAll(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NotReadyCondition(sourcev1.IndexationFailedReason, err.Error()), "", err
|
return NotReadyHelmRepository(repository, sourcev1.IndexationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
i := &repo.IndexFile{}
|
i := &repo.IndexFile{}
|
||||||
if err := yaml.Unmarshal(data, i); err != nil {
|
if err := yaml.Unmarshal(data, i); err != nil {
|
||||||
return NotReadyCondition(sourcev1.IndexationFailedReason, err.Error()), "", err
|
return NotReadyHelmRepository(repository, sourcev1.IndexationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
index, err := yaml.Marshal(i)
|
index, err := yaml.Marshal(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NotReadyCondition(sourcev1.IndexationFailedReason, err.Error()), "", err
|
return NotReadyHelmRepository(repository, sourcev1.IndexationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
sum := r.Storage.Checksum(index)
|
sum := r.Storage.Checksum(index)
|
||||||
artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(),
|
artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(),
|
||||||
fmt.Sprintf("index-%s.yaml", sum))
|
fmt.Sprintf("index-%s.yaml", sum), sum)
|
||||||
|
|
||||||
// create artifact dir
|
// create artifact dir
|
||||||
err = r.Storage.MkdirAll(artifact)
|
err = r.Storage.MkdirAll(artifact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("unable to create repository index directory: %w", err)
|
err = fmt.Errorf("unable to create repository index directory: %w", err)
|
||||||
return NotReadyCondition(sourcev1.StorageOperationFailedReason, err.Error()), "", err
|
return NotReadyHelmRepository(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// acquire lock
|
// acquire lock
|
||||||
unlock, err := r.Storage.Lock(artifact)
|
unlock, err := r.Storage.Lock(artifact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("unable to acquire lock: %w", err)
|
err = fmt.Errorf("unable to acquire lock: %w", err)
|
||||||
return NotReadyCondition(sourcev1.StorageOperationFailedReason, err.Error()), "", err
|
return NotReadyHelmRepository(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
|
@ -186,19 +176,24 @@ func (r *HelmRepositoryReconciler) index(repository sourcev1.HelmRepository) (so
|
||||||
err = r.Storage.WriteFile(artifact, index)
|
err = r.Storage.WriteFile(artifact, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("unable to write repository index file: %w", err)
|
err = fmt.Errorf("unable to write repository index file: %w", err)
|
||||||
return NotReadyCondition(sourcev1.StorageOperationFailedReason, err.Error()), "", err
|
return NotReadyHelmRepository(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
message := fmt.Sprintf("Artifact is available at %s", artifact.Path)
|
// update index symlink
|
||||||
return ReadyCondition(sourcev1.IndexationSucceededReason, message), artifact.URL, nil
|
indexUrl, err := r.Storage.Symlink(artifact, "index.yaml")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("storage error %w", err)
|
||||||
|
return NotReadyHelmRepository(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
message := fmt.Sprintf("Index is available at %s", artifact.Path)
|
||||||
|
return ReadyHelmRepository(repository, artifact, indexUrl, sourcev1.IndexationSucceededReason, message), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HelmRepositoryReconciler) shouldResetStatus(repository sourcev1.HelmRepository) (bool, sourcev1.HelmRepositoryStatus) {
|
func (r *HelmRepositoryReconciler) shouldResetStatus(repository sourcev1.HelmRepository) (bool, sourcev1.HelmRepositoryStatus) {
|
||||||
resetStatus := false
|
resetStatus := false
|
||||||
if repository.Status.Artifact != "" {
|
if repository.Status.Artifact != nil {
|
||||||
parts := strings.Split(repository.Status.Artifact, "/")
|
if !r.Storage.ArtifactExist(*repository.Status.Artifact) {
|
||||||
artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(), parts[len(parts)-1])
|
|
||||||
if !r.Storage.ArtifactExist(artifact) {
|
|
||||||
resetStatus = true
|
resetStatus = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,10 +216,8 @@ func (r *HelmRepositoryReconciler) shouldResetStatus(repository sourcev1.HelmRep
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HelmRepositoryReconciler) gc(repository sourcev1.HelmRepository) {
|
func (r *HelmRepositoryReconciler) gc(repository sourcev1.HelmRepository) {
|
||||||
if repository.Status.Artifact != "" {
|
if repository.Status.Artifact != nil {
|
||||||
parts := strings.Split(repository.Status.Artifact, "/")
|
if err := r.Storage.RemoveAllButCurrent(*repository.Status.Artifact); err != nil {
|
||||||
artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(), parts[len(parts)-1])
|
|
||||||
if err := r.Storage.RemoveAllButCurrent(artifact); err != nil {
|
|
||||||
r.Log.Info("Artifacts GC failed", "error", err)
|
r.Log.Info("Artifacts GC failed", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import (
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
||||||
"github.com/fluxcd/source-controller/internal/lockedfile"
|
"github.com/fluxcd/source-controller/internal/lockedfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,15 +45,6 @@ type Storage struct {
|
||||||
Timeout time.Duration `json:"timeout"`
|
Timeout time.Duration `json:"timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Artifact represents the output of a source synchronisation
|
|
||||||
type Artifact struct {
|
|
||||||
// Path is the local file path of this artifact
|
|
||||||
Path string `json:"path"`
|
|
||||||
|
|
||||||
// URL is the HTTP address of this artifact
|
|
||||||
URL string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStorage creates the storage helper for a given path and hostname
|
// NewStorage creates the storage helper for a given path and hostname
|
||||||
func NewStorage(basePath string, hostname string, timeout time.Duration) (*Storage, error) {
|
func NewStorage(basePath string, hostname string, timeout time.Duration) (*Storage, error) {
|
||||||
if f, err := os.Stat(basePath); os.IsNotExist(err) || !f.IsDir() {
|
if f, err := os.Stat(basePath); os.IsNotExist(err) || !f.IsDir() {
|
||||||
|
@ -67,31 +59,33 @@ func NewStorage(basePath string, hostname string, timeout time.Duration) (*Stora
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArtifactFor returns an artifact for the given Kubernetes object
|
// ArtifactFor returns an artifact for the given Kubernetes object
|
||||||
func (s *Storage) ArtifactFor(kind string, metadata metav1.Object, fileName string) Artifact {
|
func (s *Storage) ArtifactFor(kind string, metadata metav1.Object, fileName, revision string) sourcev1.Artifact {
|
||||||
path := fmt.Sprintf("%s/%s-%s/%s", kind, metadata.GetName(), metadata.GetNamespace(), fileName)
|
path := fmt.Sprintf("%s/%s-%s/%s", kind, metadata.GetName(), metadata.GetNamespace(), fileName)
|
||||||
localPath := filepath.Join(s.BasePath, path)
|
localPath := filepath.Join(s.BasePath, path)
|
||||||
url := fmt.Sprintf("http://%s/%s", s.Hostname, path)
|
url := fmt.Sprintf("http://%s/%s", s.Hostname, path)
|
||||||
|
|
||||||
return Artifact{
|
return sourcev1.Artifact{
|
||||||
Path: localPath,
|
Path: localPath,
|
||||||
URL: url,
|
URL: url,
|
||||||
|
Revision: revision,
|
||||||
|
LastUpdateTime: metav1.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MkdirAll calls os.MkdirAll for the given artifact base dir
|
// MkdirAll calls os.MkdirAll for the given artifact base dir
|
||||||
func (s *Storage) MkdirAll(artifact Artifact) error {
|
func (s *Storage) MkdirAll(artifact sourcev1.Artifact) error {
|
||||||
dir := filepath.Dir(artifact.Path)
|
dir := filepath.Dir(artifact.Path)
|
||||||
return os.MkdirAll(dir, 0777)
|
return os.MkdirAll(dir, 0777)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAll calls os.RemoveAll for the given artifact base dir
|
// RemoveAll calls os.RemoveAll for the given artifact base dir
|
||||||
func (s *Storage) RemoveAll(artifact Artifact) error {
|
func (s *Storage) RemoveAll(artifact sourcev1.Artifact) error {
|
||||||
dir := filepath.Dir(artifact.Path)
|
dir := filepath.Dir(artifact.Path)
|
||||||
return os.RemoveAll(dir)
|
return os.RemoveAll(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAllButCurrent removes all files for the given artifact base dir excluding the current one
|
// RemoveAllButCurrent removes all files for the given artifact base dir excluding the current one
|
||||||
func (s *Storage) RemoveAllButCurrent(artifact Artifact) error {
|
func (s *Storage) RemoveAllButCurrent(artifact sourcev1.Artifact) error {
|
||||||
dir := filepath.Dir(artifact.Path)
|
dir := filepath.Dir(artifact.Path)
|
||||||
errors := []string{}
|
errors := []string{}
|
||||||
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
_ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
@ -110,7 +104,7 @@ func (s *Storage) RemoveAllButCurrent(artifact Artifact) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArtifactExist returns a boolean indicating whether the artifact file exists in storage
|
// ArtifactExist returns a boolean indicating whether the artifact file exists in storage
|
||||||
func (s *Storage) ArtifactExist(artifact Artifact) bool {
|
func (s *Storage) ArtifactExist(artifact sourcev1.Artifact) bool {
|
||||||
if _, err := os.Stat(artifact.Path); os.IsNotExist(err) {
|
if _, err := os.Stat(artifact.Path); os.IsNotExist(err) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -118,7 +112,7 @@ func (s *Storage) ArtifactExist(artifact Artifact) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Archive creates a tar.gz to the artifact path from the given dir excluding the provided file extensions
|
// Archive creates a tar.gz to the artifact path from the given dir excluding the provided file extensions
|
||||||
func (s *Storage) Archive(artifact Artifact, dir string, excludes string) error {
|
func (s *Storage) Archive(artifact sourcev1.Artifact, dir string, excludes string) error {
|
||||||
if excludes == "" {
|
if excludes == "" {
|
||||||
excludes = "jpg,jpeg,gif,png,wmv,flv,tar.gz,zip"
|
excludes = "jpg,jpeg,gif,png,wmv,flv,tar.gz,zip"
|
||||||
}
|
}
|
||||||
|
@ -138,7 +132,7 @@ func (s *Storage) Archive(artifact Artifact, dir string, excludes string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteFile writes the given bytes to the artifact path if the checksum differs
|
// WriteFile writes the given bytes to the artifact path if the checksum differs
|
||||||
func (s *Storage) WriteFile(artifact Artifact, data []byte) error {
|
func (s *Storage) WriteFile(artifact sourcev1.Artifact, data []byte) error {
|
||||||
sum := s.Checksum(data)
|
sum := s.Checksum(data)
|
||||||
if file, err := os.Stat(artifact.Path); !os.IsNotExist(err) && !file.IsDir() {
|
if file, err := os.Stat(artifact.Path); !os.IsNotExist(err) && !file.IsDir() {
|
||||||
if fb, err := ioutil.ReadFile(artifact.Path); err == nil && sum == s.Checksum(fb) {
|
if fb, err := ioutil.ReadFile(artifact.Path); err == nil && sum == s.Checksum(fb) {
|
||||||
|
@ -150,24 +144,27 @@ func (s *Storage) WriteFile(artifact Artifact, data []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Symlink creates or updates a symbolic link for the given artifact
|
// Symlink creates or updates a symbolic link for the given artifact
|
||||||
func (s *Storage) Symlink(artifact Artifact, linkName string) error {
|
// and returns the URL for the symlink
|
||||||
|
func (s *Storage) Symlink(artifact sourcev1.Artifact, linkName string) (string, error) {
|
||||||
dir := filepath.Dir(artifact.Path)
|
dir := filepath.Dir(artifact.Path)
|
||||||
link := filepath.Join(dir, linkName)
|
link := filepath.Join(dir, linkName)
|
||||||
tmpLink := link + ".tmp"
|
tmpLink := link + ".tmp"
|
||||||
|
|
||||||
if err := os.Remove(tmpLink); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(tmpLink); err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Symlink(artifact.Path, tmpLink); err != nil {
|
if err := os.Symlink(artifact.Path, tmpLink); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Rename(tmpLink, link); err != nil {
|
if err := os.Rename(tmpLink, link); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
parts := strings.Split(artifact.URL, "/")
|
||||||
|
url := strings.Replace(artifact.URL, parts[len(parts)-1], linkName, 1)
|
||||||
|
return url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checksum returns the SHA1 checksum for the given bytes as a string
|
// Checksum returns the SHA1 checksum for the given bytes as a string
|
||||||
|
@ -176,7 +173,7 @@ func (s *Storage) Checksum(b []byte) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock creates a file lock for the given artifact
|
// Lock creates a file lock for the given artifact
|
||||||
func (s *Storage) Lock(artifact Artifact) (unlock func(), err error) {
|
func (s *Storage) Lock(artifact sourcev1.Artifact) (unlock func(), err error) {
|
||||||
lockFile := artifact.Path + ".lock"
|
lockFile := artifact.Path + ".lock"
|
||||||
mutex := lockedfile.MutexAt(lockFile)
|
mutex := lockedfile.MutexAt(lockFile)
|
||||||
return mutex.Lock()
|
return mutex.Lock()
|
||||||
|
|
Loading…
Reference in New Issue