argoproj / argo-cd

Declarative Continuous Deployment for Kubernetes
https://argo-cd.readthedocs.io
Apache License 2.0
17.48k stars 5.32k forks source link

Argo CD Notifications Slack: repo.GetCommitMetadata fails to interpret .app.status.sync.revisions #13281

Open northonheld opened 1 year ago

northonheld commented 1 year ago

Describe the bug

When I try to run this way {{(call .repo.getCommitMetadata .app.status.sync.revisions).Author}} I get this error <call .repo.GetCommitMetadata .app.status.sync.revisions>: error calling call: arg 0: value has type []interface {}; should be string"

I am using the new feature called "sources".

When I am using this option I notice that all paths within the status where the information is has become a list. Source changed to Soureces and revision changed to revisions. But repo.GetCommitMetadata can't handle lists and now I only have available the status revisions.

My argocd-notifications-cm configuration:

  template.app-deployed: |
    message: |
      {{if eq .serviceType "slack"}}:white_check_mark:{{end}} Application {{.app.metadata.name}} is now running new version of deployments manifests.
    slack:
      attachments: |
        [{
          "title": "{{ .app.metadata.name}}",
          "title_link":"{{.context.argocdUrl}}/applications/{{.app.metadata.name}}",
          "color": "#18be52",
          "fields": [
          {
            "title": "Sync Status",
            "value": "{{.app.status.sync.status}}",
            "short": true
          },
          {
            "title": "Revision",
            "value": "{{.app.status.sync.revisions}}",
            "short": true
          },
          {
              "type": "mrkdwn",
              "text": "*Author:*\n<@{{(call .repo.GetCommitMetadata .app.status.sync.revisions).Author}}>"
          }
          {{range $index, $c := .app.spec.sources}}
          {{if not $index}},{{end}}
          {{if $index}},{{end}}
          {
            "title": "Repository",
            "value": "{{$c.repoURL}}",
            "short": true
          }
          {{end}}
          {{range $index, $c := .app.status.conditions}}
          {{if not $index}},{{end}}
          {{if $index}},{{end}}
          {
            "title": "{{$c.type}}",
            "value": "{{$c.message}}",
            "short": true
          }
          {{end}}
          ]
        }]
      deliveryPolicy: Post
      groupingKey: ""
      notifyBroadcast: false

I found it and tried to configure it as shown in this link, but without success https://github.com/argoproj/argo-cd/discussions/10573

To Reproduce

Just use the Sources functionality in your Application

Expected behavior

I want to be able to pass a list of revisions to .repo.GetCommitMetadata using .app.status.sync.revisions

Screenshots

When I use only the path to revisions:

          {
            "title": "Revision",
            "value": "{{.app.status.sync.revisions}}",
            "short": true
          },

Captura de Tela 2023-04-19 às 00 43 29

New Aplication status: Captura de Tela 2023-04-19 às 00 46 34

Version

argocd: v2.6.3+e05298b.dirty
  BuildDate: 2023-02-27T16:13:38Z
  GitCommit: e05298b9c6ab8610104271fa8491f019fee3c587
  GitTreeState: dirty
  GoVersion: go1.20.1
  Compiler: gc
  Platform: darwin/amd64
argocd-server: v2.6.5+60104ac
  BuildDate: 2023-03-14T14:19:45Z
  GitCommit: 60104aca6faec73d10d57cb38245d47f4fb3146b
  GitTreeState: clean
  GoVersion: go1.18.10
  Compiler: gc
  Platform: linux/amd64
  Kustomize Version: v4.5.7 2022-08-02T16:35:54Z
  Helm Version: v3.10.3+g835b733
  Kubectl Version: v0.24.2
  Jsonnet Version: v0.19.1

Logs

argocd-notifications-controller-76fb8454c5-sbwzz notifications-controller time="2023-04-19T02:35:27Z" level=error msg="Failed to notify recipient {slack dev-staging} defined in resource argocd/httpbin: template: app-deployed:18:17: executing \"app-deployed\" at <call .repo.GetCommitMetadata .app.status.sync.revisions>: error calling call: arg 0: value has type []interface {}; should be string" resource=argocd/httpbin
northonheld commented 1 year ago

me again,

I found another way to get the revision information like this path .app.status.operationState.operation.sync.revision

But I got another error like this here<call .repo.GetCommitMetadata .app.status.operationState.operation.sync.revision>: error calling call: failed to get application source repo URL

Looking at the source code I noticed that the URL is being created using ""source", so the URL will always be empty. The new path to the URL is .app.spec.sources.repoURL. But as said before this path is an array

func getCommitMetadata(commitSHA string, app *unstructured.Unstructured, argocdService argocd.Service) (*shared.CommitMetadata, error) {
    repoURL, ok, err := unstructured.NestedString(app.Object, "spec", "source", "repoURL")
    if err != nil {
        return nil, err
    }
    if !ok {
        panic(errors.New("failed to get application source repo URL"))
    }
    meta, err := argocdService.GetCommitMetadata(context.Background(), repoURL, commitSHA)
    if err != nil {
        return nil, err
    }
    return meta, nil
}

The problem is in this line repoURL, ok, err := unstructured.NestedString(app.Object, "spec", "source", "repoURL")

But as this involves other things, I believe that this Func needs to be refactored and include conditionals to handle arrays or to know the distinction between Source and Sources, or even to receive the URL as a parameter

hunsche commented 1 year ago

@crenshaw-dev I believe it enters the label multi-source-apps.

bianchi2 commented 1 year ago

I naively hoped this will fly:

{{(call .repo.GetCommitMetadata (index .app.status.sync.revisions 1)).Author}}

It didn't - error calling call: failed to get application source repo URL

gburke-ppb commented 1 year ago

I naively hoped this will fly:

{{(call .repo.GetCommitMetadata (index .app.status.sync.revisions 1)).Author}}

It didn't - error calling call: failed to get application source repo URL

The array will be zero indexed, so try

{{(call .repo.GetCommitMetadata (index .app.status.sync.revisions 0)).Author}}

or

{{(call .repo.GetCommitMetadata (first .app.status.sync.revisions)).Author}}
tomikonio commented 12 months ago

@gburke-ppb Hi, Tried it, but it does not work as well Getting the error:

error calling call: failed to get application source repo URL
gburke-ppb commented 12 months ago

Looking at the code: https://github.com/argoproj/argo-cd/blob/80d1bb87e02ce9b6f526f5e15141fa9799e41871/util/notification/expression/repo/repo.go#L51 It appears that that function is looking for app.spec.source.repoURL, assuming a single source and not app.sources[]. So if you are using multiple sources in your Application, getCommitMetadata is useless. It needs to be updated for sources. The revisions are in the same order as the sources so I guess it should be easy enough to say 'this SHA is the 2nd revision in the list, so this matches the 2nd source in the list of sources' and pick up the repoURL from there.

But it doesn't. So it's a... bug? feature? oversight?

However, you appear to only have one source anyway, so why are you using sources and not source in your Application? If you change that to source it will likely work fine with {(call .repo.GetCommitMetadata .app.status.sync.revision).Author}}

murtll commented 2 months ago

+1 on this Any progress here?

I am not sure what would be the best option to address the issue, but have ideas on this:

  1. Add a new template function such as repo.GetCommitMetadataByRepoUrl to be called like this

    call .repo.GetCommitMetadataByRepoUrl (index .app.spec.sources 1).repoURL (index .app.status.sync.revisions 1)

    or even repo.GetCommitMetadataBySource to pass source index

    call .repo.GetCommitMetadataBySource 1 (index .app.status.sync.revisions 1)
  2. Update current repo.GetCommitMetadata to search for commit in all sources of type git. The first source where commit appears to exist, is used as source of metadata returned. Would be not that explicit, but easier to use.

  3. Add function repo.GetMultiCommitMetadata to take an array of revisions as an argument, so there would be ability to call repo.GetMultiCommitMetadata .app.status.sync.revisions and receive an array of commit metadata back. Can be used like this

    (index (call .repo.GetMultiCommitMetadata .app.status.sync.revisions) 1)

    If type of source is not git, then null or empty object is returned.

It needs discussion I think, if some option will be considered good to implement, I would be happy to do it.

ddeath commented 1 month ago

Hi. What you can do as a partial workaround is to define source as well as sources in your Application manifest:

  source:
    repoURL: {{ $service.repoUrl }}
  sources:
    - repoURL: HELM_REPO_URL_HERE
      chart: microservice-helm-chart
      targetRevision: {{ $service.helmVersionOverride }}
      helm:
        valueFiles:
          - $values/{{ $service.valueFilesLocation }}/values.yaml
          - $values/{{ $service.valueFilesLocation }}/values.dev.yaml
        ignoreMissingValueFiles: true
    - repoURL: {{ $service.repoUrl }}
      path: {{ $service.valueFilesLocation }}
      targetRevision: {{ $service.branch }}
      ref: values

And then {{ (call .repo.GetCommitMetadata (index .app.status.operationState.operation.sync.revisions 1)).Author }} will work for you...

However you can choose only 1 repo for which you will get commit metadata... (because you can defined only 1 in source prop)

This is working for me and should not have too much side effects because docs are saying:

You can provide multiple sources using the sources field. When you specify the sources field, Argo CD will ignore the source (singular) field.