Skip to content

Commit

Permalink
Support multiple sources and revisions (#955)
Browse files Browse the repository at this point in the history
* Support multiple sources and revisions (ApplicationPhaseCommentReconciler)

* Support multiple sources and revisions (ApplicationHealthCommentReconciler)

* Replace with `sources` in `Application`

* Fix e2e-test

* Change to app3
  • Loading branch information
int128 authored Sep 2, 2023
1 parent 3609f35 commit 35ff041
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 69 deletions.
9 changes: 5 additions & 4 deletions e2e_test/applications/app3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ metadata:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/int128/argocd-commenter-e2e-test
targetRevision: FIXTURE_BASE_BRANCH
path: app3
# https://argo-cd.readthedocs.io/en/stable/user-guide/multiple_sources/
sources:
- repoURL: https://github.com/int128/argocd-commenter-e2e-test
targetRevision: FIXTURE_BASE_BRANCH
path: app3
destination:
server: https://kubernetes.default.svc
namespace: test3-fixture
Expand Down
4 changes: 4 additions & 0 deletions e2e_test/waitforapp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ func newApplicationStatus(app *argocdv1alpha1.Application) ApplicationStatus {
Sync: string(app.Status.Sync.Status),
Health: string(app.Status.Health.Status),
}
// for multiple sources
if len(app.Status.Sync.Revisions) > 0 {
s.Revision = app.Status.Sync.Revisions[0]
}
if app.Status.OperationState != nil {
s.Operation = string(app.Status.OperationState.Phase)
}
Expand Down
33 changes: 26 additions & 7 deletions internal/argocd/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,34 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// GetDeployedRevision returns the last synced revision
func GetDeployedRevision(a argocdv1alpha1.Application) string {
if a.Status.OperationState == nil {
return ""
type SourceRevision struct {
Source argocdv1alpha1.ApplicationSource
Revision string
}

// GetSourceRevisions returns the last synced revisions
func GetSourceRevisions(app argocdv1alpha1.Application) []SourceRevision {
if app.Status.OperationState == nil {
return nil
}
if a.Status.OperationState.Operation.Sync == nil {
return ""
if app.Status.OperationState.Operation.Sync == nil {
return nil
}
sources := app.Spec.GetSources()
revisions := app.Status.OperationState.Operation.Sync.Revisions
if revisions == nil {
revisions = []string{app.Status.OperationState.Operation.Sync.Revision}
}
size := min(len(sources), len(revisions))

sourceRevisions := make([]SourceRevision, size)
for i := 0; i < size; i++ {
sourceRevisions[i] = SourceRevision{
Source: sources[i],
Revision: revisions[i],
}
}
return a.Status.OperationState.Operation.Sync.Revision
return sourceRevisions
}

// GetDeploymentURL returns the deployment URL in annotations
Expand Down
32 changes: 18 additions & 14 deletions internal/controller/applicationhealthcomment_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ func (r *ApplicationHealthCommentReconciler) Reconcile(ctx context.Context, req
if !app.DeletionTimestamp.IsZero() {
return ctrl.Result{}, nil
}
deployedRevision := argocd.GetDeployedRevision(app)

var appHealth argocdcommenterv1.ApplicationHealth
if err := r.Client.Get(ctx, req.NamespacedName, &appHealth); err != nil {
Expand All @@ -81,39 +80,44 @@ func (r *ApplicationHealthCommentReconciler) Reconcile(ctx context.Context, req
}
logger.Info("created an ApplicationHealth")
}
if deployedRevision == appHealth.Status.LastHealthyRevision {
return ctrl.Result{}, nil
}

argocdURL, err := argocd.GetExternalURL(ctx, r.Client, req.Namespace)
if err != nil {
logger.Info("unable to determine Argo CD URL", "error", err)
}
comment := notification.NewCommentOnOnHealthChanged(app, argocdURL)
if comment == nil {
comments := notification.NewCommentsOnOnHealthChanged(app, argocdURL)
if len(comments) == 0 {
logger.Info("no comment on this health event")
return ctrl.Result{}, nil
}
if err := r.Notification.CreateComment(ctx, *comment, app); err != nil {
logger.Error(err, "unable to create a comment")
r.Recorder.Eventf(&app, corev1.EventTypeWarning, "CreateCommentError",
"unable to create a comment by %s: %s", app.Status.Health.Status, err)
} else {
r.Recorder.Eventf(&app, corev1.EventTypeNormal, "CreatedComment", "created a comment by %s", app.Status.Health.Status)
currentRevision := comments[0].Revision
if appHealth.Status.LastHealthyRevision == currentRevision {
logger.Info("current revision is already healthy", "revision", currentRevision)
return ctrl.Result{}, nil
}

for _, comment := range comments {
if err := r.Notification.CreateComment(ctx, comment, app); err != nil {
logger.Error(err, "unable to create a comment")
r.Recorder.Eventf(&app, corev1.EventTypeWarning, "CreateCommentError",
"unable to create a comment by %s: %s", app.Status.Health.Status, err)
} else {
r.Recorder.Eventf(&app, corev1.EventTypeNormal, "CreatedComment", "created a comment by %s", app.Status.Health.Status)
}
}

if app.Status.Health.Status != health.HealthStatusHealthy {
return ctrl.Result{}, nil
}
patch := client.MergeFrom(appHealth.DeepCopy())
appHealth.Status.LastHealthyRevision = deployedRevision
appHealth.Status.LastHealthyRevision = currentRevision
if err := r.Client.Status().Patch(ctx, &appHealth, patch); err != nil {
logger.Error(err, "unable to patch lastHealthyRevision of ApplicationHealth")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
logger.Info("patched lastHealthyRevision of ApplicationHealth")
r.Recorder.Eventf(&appHealth, corev1.EventTypeNormal, "UpdateLastHealthyRevision",
"patched lastHealthyRevision to %s", deployedRevision)
"patched lastHealthyRevision to %s", currentRevision)
return ctrl.Result{}, nil
}

Expand Down
18 changes: 10 additions & 8 deletions internal/controller/applicationphasecomment_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,19 @@ func (r *ApplicationPhaseCommentReconciler) Reconcile(ctx context.Context, req c
if err != nil {
logger.Info("unable to determine Argo CD URL", "error", err)
}
comment := notification.NewCommentOnOnPhaseChanged(app, argocdURL)
if comment == nil {
comments := notification.NewCommentsOnOnPhaseChanged(app, argocdURL)
if len(comments) == 0 {
logger.Info("no comment on this phase event", "phase", phase)
return ctrl.Result{}, nil
}
if err := r.Notification.CreateComment(ctx, *comment, app); err != nil {
logger.Error(err, "unable to create a comment")
r.Recorder.Eventf(&app, corev1.EventTypeWarning, "CreateCommentError",
"unable to create a comment by %s: %s", phase, err)
} else {
r.Recorder.Eventf(&app, corev1.EventTypeNormal, "CreatedComment", "created a comment by %s", phase)
for _, comment := range comments {
if err := r.Notification.CreateComment(ctx, comment, app); err != nil {
logger.Error(err, "unable to create a comment")
r.Recorder.Eventf(&app, corev1.EventTypeWarning, "CreateCommentError",
"unable to create a comment by %s: %s", phase, err)
} else {
r.Recorder.Eventf(&app, corev1.EventTypeNormal, "CreatedComment", "created a comment by %s", phase)
}
}
return ctrl.Result{}, nil
}
Expand Down
81 changes: 45 additions & 36 deletions internal/notification/comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,60 +20,64 @@ type Comment struct {
Body string
}

func NewCommentOnOnPhaseChanged(app argocdv1alpha1.Application, argocdURL string) *Comment {
if app.Spec.Source == nil {
return nil
func NewCommentsOnOnPhaseChanged(app argocdv1alpha1.Application, argocdURL string) []Comment {
sourceRevisions := argocd.GetSourceRevisions(app)
var comments []Comment
for _, sourceRevision := range sourceRevisions {
comment := generateCommentOnPhaseChanged(app, argocdURL, sourceRevision)
if comment == nil {
continue
}
comments = append(comments, *comment)
}
repository := github.ParseRepositoryURL(app.Spec.Source.RepoURL)
return comments
}

func generateCommentOnPhaseChanged(app argocdv1alpha1.Application, argocdURL string, sourceRevision argocd.SourceRevision) *Comment {
repository := github.ParseRepositoryURL(sourceRevision.Source.RepoURL)
if repository == nil {
return nil
}
revision := argocd.GetDeployedRevision(app)
if revision == "" {
return nil
}
body := generateCommentOnPhaseChanged(app, argocdURL)
body := generateCommentBodyOnPhaseChanged(app, argocdURL, sourceRevision)
if body == "" {
return nil
}
return &Comment{
GitHubRepository: *repository,
Revision: revision,
Revision: sourceRevision.Revision,
Body: body,
}
}

func generateCommentOnPhaseChanged(app argocdv1alpha1.Application, argocdURL string) string {
phase := argocd.GetOperationPhase(app)
if phase == "" {
func generateCommentBodyOnPhaseChanged(app argocdv1alpha1.Application, argocdURL string, sourceRevision argocd.SourceRevision) string {
if app.Status.OperationState == nil {
return ""
}
revision := argocd.GetDeployedRevision(app)
argocdApplicationURL := fmt.Sprintf("%s/applications/%s", argocdURL, app.Name)

phase := app.Status.OperationState.Phase
switch phase {
case synccommon.OperationRunning:
return fmt.Sprintf(":warning: Syncing [%s](%s) to %s", app.Name, argocdApplicationURL, revision)
return fmt.Sprintf(":warning: Syncing [%s](%s) to %s", app.Name, argocdApplicationURL, sourceRevision.Revision)
case synccommon.OperationSucceeded:
return fmt.Sprintf(":white_check_mark: Synced [%s](%s) to %s", app.Name, argocdApplicationURL, revision)
return fmt.Sprintf(":white_check_mark: Synced [%s](%s) to %s", app.Name, argocdApplicationURL, sourceRevision.Revision)
case synccommon.OperationFailed, synccommon.OperationError:
return fmt.Sprintf("## :x: Sync %s: [%s](%s)\nError while syncing to %s:\n%s",
phase,
app.Name,
argocdApplicationURL,
revision,
generateSyncResultComment(app),
sourceRevision.Revision,
generateSyncResultComment(app.Status.OperationState.SyncResult),
)
}
return ""
}

func generateSyncResultComment(app argocdv1alpha1.Application) string {
if app.Status.OperationState.SyncResult == nil {
func generateSyncResultComment(syncResult *argocdv1alpha1.SyncOperationResult) string {
if syncResult == nil {
return ""
}
var b strings.Builder
for _, r := range app.Status.OperationState.SyncResult.Resources {
for _, r := range syncResult.Resources {
namespacedName := r.Namespace + "/" + r.Name
switch r.Status {
case synccommon.ResultCodeSyncFailed, synccommon.ResultCodePruneSkipped:
Expand All @@ -83,31 +87,36 @@ func generateSyncResultComment(app argocdv1alpha1.Application) string {
return b.String()
}

func NewCommentOnOnHealthChanged(app argocdv1alpha1.Application, argocdURL string) *Comment {
if app.Spec.Source == nil {
return nil
func NewCommentsOnOnHealthChanged(app argocdv1alpha1.Application, argocdURL string) []Comment {
sourceRevisions := argocd.GetSourceRevisions(app)
var comments []Comment
for _, sourceRevision := range sourceRevisions {
comment := generateCommentOnHealthChanged(app, argocdURL, sourceRevision)
if comment == nil {
continue
}
comments = append(comments, *comment)
}
repository := github.ParseRepositoryURL(app.Spec.Source.RepoURL)
return comments
}

func generateCommentOnHealthChanged(app argocdv1alpha1.Application, argocdURL string, sourceRevision argocd.SourceRevision) *Comment {
repository := github.ParseRepositoryURL(sourceRevision.Source.RepoURL)
if repository == nil {
return nil
}
revision := argocd.GetDeployedRevision(app)
if revision == "" {
return nil
}
body := generateCommentOnHealthChanged(app, argocdURL)
body := generateCommentBodyOnHealthChanged(app, argocdURL, sourceRevision)
if body == "" {
return nil
}
return &Comment{
GitHubRepository: *repository,
Revision: revision,
Revision: sourceRevision.Revision,
Body: body,
}
}

func generateCommentOnHealthChanged(app argocdv1alpha1.Application, argocdURL string) string {
revision := argocd.GetDeployedRevision(app)
func generateCommentBodyOnHealthChanged(app argocdv1alpha1.Application, argocdURL string, sourceRevision argocd.SourceRevision) string {
argocdApplicationURL := fmt.Sprintf("%s/applications/%s", argocdURL, app.Name)
switch app.Status.Health.Status {
case health.HealthStatusHealthy:
Expand All @@ -116,15 +125,15 @@ func generateCommentOnHealthChanged(app argocdv1alpha1.Application, argocdURL st
app.Status.Health.Status,
app.Name,
argocdApplicationURL,
revision,
sourceRevision.Revision,
)
case health.HealthStatusDegraded:
return fmt.Sprintf("## %s %s: [%s](%s)\nDeployed %s",
":x:",
app.Status.Health.Status,
app.Name,
argocdApplicationURL,
revision,
sourceRevision.Revision,
)
}
return ""
Expand Down

0 comments on commit 35ff041

Please sign in to comment.