diff --git a/controllers/conditions.go b/controllers/conditions.go index f151ae67..13579aa9 100644 --- a/controllers/conditions.go +++ b/controllers/conditions.go @@ -42,3 +42,61 @@ func NotReadyCondition(reason, message string) sourcev1.SourceCondition { 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 "" +} diff --git a/controllers/gitrepository_controller.go b/controllers/gitrepository_controller.go index 5b38bfd2..21084c56 100644 --- a/controllers/gitrepository_controller.go +++ b/controllers/gitrepository_controller.go @@ -83,28 +83,19 @@ func (r *GitRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro r.gc(repo) // try git clone - readyCondition, artifacts, err := r.sync(repo) + syncedRepo, err := r.sync(*repo.DeepCopy()) if err != nil { 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 - readyCondition.LastTransitionTime = metav1.Now() - repo.Status.Conditions = []sourcev1.SourceCondition{readyCondition} - - if err := r.Status().Update(ctx, &repo); err != nil { + if err := r.Status().Update(ctx, &syncedRepo); err != nil { log.Error(err, "unable to update GitRepository status") return result, err } + log.Info("Repository sync succeeded", "msg", GitRepositoryReadyMessage(syncedRepo)) + // requeue repository return result, nil } @@ -116,7 +107,7 @@ func (r *GitRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error { WithEventFilter(predicate.Funcs{ DeleteFunc: func(e event.DeleteEvent) bool { // 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 { r.Log.Error(err, "unable to delete artifacts", 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) } -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 branch := "master" + revision := "" tagMode := git.NoTags depth := 2 @@ -159,21 +151,21 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc tmpSSH, err := ioutil.TempDir("", repository.Name) if err != nil { 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) auth, err := r.auth(repository, tmpSSH) if err != nil { 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 tmpGit, err := ioutil.TempDir("", repository.Name) if err != nil { 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) @@ -192,7 +184,7 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc }) if err != nil { 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 @@ -201,7 +193,7 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc w, err := repo.Worktree() if err != nil { 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{ @@ -210,19 +202,19 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc }) if err != nil { 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 != "" { rng, err := semver.ParseRange(exp) if err != nil { 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() if err != nil { 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) @@ -246,11 +238,12 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc v := svers[len(svers)-1] t := svTags[v.String()] commit := tags[t] + revision = fmt.Sprintf("%s/%s", t, commit) w, err := repo.Worktree() if err != nil { 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{ @@ -258,11 +251,11 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc }) if err != nil { err = fmt.Errorf("git checkout error: %w", err) - return NotReadyCondition(sourcev1.GitOperationFailedReason, err.Error()), "", err + return NotReadyGitRepository(repository, 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 + return NotReadyGitRepository(repository, sourcev1.GitOperationFailedReason, err.Error()), err } } } @@ -271,24 +264,28 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc ref, err := repo.Head() if err != nil { 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(), - fmt.Sprintf("%s.tar.gz", ref.Hash().String())) + fmt.Sprintf("%s.tar.gz", ref.Hash().String()), revision) // create artifact dir err = r.Storage.MkdirAll(artifact) if err != nil { 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 unlock, err := r.Storage.Lock(artifact) if err != nil { 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() @@ -296,26 +293,24 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc err = r.Storage.Archive(artifact, tmpGit, "") if err != nil { 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 - err = r.Storage.Symlink(artifact, "latest.tar.gz") + url, err := r.Storage.Symlink(artifact, "latest.tar.gz") if err != nil { 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) - 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) { resetStatus := false - if repository.Status.Artifact != "" { - parts := strings.Split(repository.Status.Artifact, "/") - artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(), parts[len(parts)-1]) - if !r.Storage.ArtifactExist(artifact) { + if repository.Status.Artifact != nil { + if !r.Storage.ArtifactExist(*repository.Status.Artifact) { resetStatus = true } } @@ -338,10 +333,8 @@ func (r *GitRepositoryReconciler) shouldResetStatus(repository sourcev1.GitRepos } func (r *GitRepositoryReconciler) gc(repository sourcev1.GitRepository) { - if repository.Status.Artifact != "" { - parts := strings.Split(repository.Status.Artifact, "/") - artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(), parts[len(parts)-1]) - if err := r.Storage.RemoveAllButCurrent(artifact); err != nil { + if repository.Status.Artifact != nil { + if err := r.Storage.RemoveAllButCurrent(*repository.Status.Artifact); err != nil { r.Log.Info("Artifacts GC failed", "error", err) } } @@ -370,9 +363,13 @@ func (r *GitRepositoryReconciler) auth(repository sourcev1.GitRepository, tmp st auth := &http.BasicAuth{} if username, ok := credentials["username"]; ok { 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 { auth.Password = string(password) + } else { + return nil, fmt.Errorf("%s secret does not contain password", repository.Spec.SecretRef.Name) } if auth.Username == "" || auth.Password == "" { diff --git a/controllers/helmrepository_controller.go b/controllers/helmrepository_controller.go index 57965f76..6954e4cf 100644 --- a/controllers/helmrepository_controller.go +++ b/controllers/helmrepository_controller.go @@ -22,7 +22,6 @@ import ( "io/ioutil" "net/url" "path" - "strings" "time" "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) defer cancel() - log := r.Log.WithValues("helmrepository", req.NamespacedName) + log := r.Log.WithValues(r.Kind, req.NamespacedName) var repository sourcev1.HelmRepository 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) // try to download index - readyCondition, artifact, err := r.index(repository) + syncedRepo, err := r.sync(*repository.DeepCopy()) if err != nil { 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 - readyCondition.LastTransitionTime = metav1.Now() - repository.Status.Conditions = []sourcev1.SourceCondition{readyCondition} - - if err := r.Status().Update(ctx, &repository); err != nil { + if err := r.Status().Update(ctx, &syncedRepo); err != nil { log.Error(err, "unable to update HelmRepository status") return result, err } + log.Info("Repository sync succeeded", "msg", HelmRepositoryReadyMessage(syncedRepo)) + // requeue repository return result, nil } @@ -113,7 +103,7 @@ func (r *HelmRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error { WithEventFilter(predicate.Funcs{ DeleteFunc: func(e event.DeleteEvent) bool { // 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 { r.Log.Error(err, "unable to delete artifacts", 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) } -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) 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) 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") @@ -145,40 +135,40 @@ func (r *HelmRepositoryReconciler) index(repository sourcev1.HelmRepository) (so // TODO(hidde): add authentication config res, err := c.Get(indexURL, getter.WithURL(repository.Spec.URL)) if err != nil { - return NotReadyCondition(sourcev1.IndexationFailedReason, err.Error()), "", err + return NotReadyHelmRepository(repository, sourcev1.IndexationFailedReason, err.Error()), err } data, err := ioutil.ReadAll(res) if err != nil { - return NotReadyCondition(sourcev1.IndexationFailedReason, err.Error()), "", err + return NotReadyHelmRepository(repository, sourcev1.IndexationFailedReason, err.Error()), err } i := &repo.IndexFile{} 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) if err != nil { - return NotReadyCondition(sourcev1.IndexationFailedReason, err.Error()), "", err + return NotReadyHelmRepository(repository, sourcev1.IndexationFailedReason, err.Error()), err } sum := r.Storage.Checksum(index) 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 err = r.Storage.MkdirAll(artifact) if err != nil { 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 unlock, err := r.Storage.Lock(artifact) if err != nil { 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() @@ -186,19 +176,24 @@ func (r *HelmRepositoryReconciler) index(repository sourcev1.HelmRepository) (so err = r.Storage.WriteFile(artifact, index) if err != nil { 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) - return ReadyCondition(sourcev1.IndexationSucceededReason, message), artifact.URL, nil + // update index symlink + 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) { resetStatus := false - if repository.Status.Artifact != "" { - parts := strings.Split(repository.Status.Artifact, "/") - artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(), parts[len(parts)-1]) - if !r.Storage.ArtifactExist(artifact) { + if repository.Status.Artifact != nil { + if !r.Storage.ArtifactExist(*repository.Status.Artifact) { resetStatus = true } } @@ -221,10 +216,8 @@ func (r *HelmRepositoryReconciler) shouldResetStatus(repository sourcev1.HelmRep } func (r *HelmRepositoryReconciler) gc(repository sourcev1.HelmRepository) { - if repository.Status.Artifact != "" { - parts := strings.Split(repository.Status.Artifact, "/") - artifact := r.Storage.ArtifactFor(r.Kind, repository.ObjectMeta.GetObjectMeta(), parts[len(parts)-1]) - if err := r.Storage.RemoveAllButCurrent(artifact); err != nil { + if repository.Status.Artifact != nil { + if err := r.Storage.RemoveAllButCurrent(*repository.Status.Artifact); err != nil { r.Log.Info("Artifacts GC failed", "error", err) } } diff --git a/controllers/storage.go b/controllers/storage.go index b42c5d49..f9198cc2 100644 --- a/controllers/storage.go +++ b/controllers/storage.go @@ -29,6 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1" "github.com/fluxcd/source-controller/internal/lockedfile" ) @@ -44,15 +45,6 @@ type Storage struct { 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 func NewStorage(basePath string, hostname string, timeout time.Duration) (*Storage, error) { 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 -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) localPath := filepath.Join(s.BasePath, path) url := fmt.Sprintf("http://%s/%s", s.Hostname, path) - return Artifact{ - Path: localPath, - URL: url, + return sourcev1.Artifact{ + Path: localPath, + URL: url, + Revision: revision, + LastUpdateTime: metav1.Now(), } } // 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) return os.MkdirAll(dir, 0777) } // 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) return os.RemoveAll(dir) } // 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) errors := []string{} _ = 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 -func (s *Storage) ArtifactExist(artifact Artifact) bool { +func (s *Storage) ArtifactExist(artifact sourcev1.Artifact) bool { if _, err := os.Stat(artifact.Path); os.IsNotExist(err) { 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 -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 == "" { 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 -func (s *Storage) WriteFile(artifact Artifact, data []byte) error { +func (s *Storage) WriteFile(artifact sourcev1.Artifact, data []byte) error { sum := s.Checksum(data) 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) { @@ -150,24 +144,27 @@ func (s *Storage) WriteFile(artifact Artifact, data []byte) error { } // 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) link := filepath.Join(dir, linkName) tmpLink := link + ".tmp" if err := os.Remove(tmpLink); err != nil && !os.IsNotExist(err) { - return err + return "", err } if err := os.Symlink(artifact.Path, tmpLink); err != nil { - return err + return "", err } 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 @@ -176,7 +173,7 @@ func (s *Storage) Checksum(b []byte) string { } // 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" mutex := lockedfile.MutexAt(lockFile) return mutex.Lock()