Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple sources and revisions #955

Merged
merged 6 commits into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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