Skip to content

Commit

Permalink
Add msadaptivecard Provider
Browse files Browse the repository at this point in the history
Signed-off-by: Matheus Pimenta <[email protected]>
  • Loading branch information
matheuscscp committed Sep 6, 2024
1 parent b81755d commit 534ee7e
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 1 deletion.
3 changes: 2 additions & 1 deletion api/v1beta3/provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ const (
PagerDutyProvider string = "pagerduty"
DataDogProvider string = "datadog"
NATSProvider string = "nats"
MSAdaptiveCardProvider string = "msadaptivecard"
)

// ProviderSpec defines the desired state of the Provider.
type ProviderSpec struct {
// Type specifies which Provider implementation to use.
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;gitea;bitbucketserver;bitbucket;azuredevops;googlechat;googlepubsub;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;grafana;githubdispatch;pagerduty;datadog;nats
// +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;gitea;bitbucketserver;bitbucket;azuredevops;googlechat;googlepubsub;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;grafana;githubdispatch;pagerduty;datadog;nats;msadaptivecard
// +required
Type string `json:"type"`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ spec:
- pagerduty
- datadog
- nats
- msadaptivecard
type: string
username:
description: Username specifies the name under which events are posted.
Expand Down
184 changes: 184 additions & 0 deletions internal/notifier/adaptive_card.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
Copyright 2024 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package notifier

import (
"context"
"crypto/x509"
"fmt"
"net/url"
"strings"

eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
)

// MSAdapativeCard holds the configuration for talking to an endpoint that accepts
// an MS Adaptive Card payload.
type MSAdapativeCard struct {
URL string
ProxyURL string
CertPool *x509.CertPool
}

// The payload structures below reflect this documentation:
// https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL%2Ctext1#send-adaptive-cards-using-an-incoming-webhook

// MSAdaptiveCardMessage is the message payload for MS Adaptive Card.
type MSAdaptiveCardMessage struct {
Type string `json:"type"`
Attachments []MSAdaptiveCardAttachment `json:"attachments"`
}

// MSAdaptiveCardAttachment is the attachment payload for MS Adaptive Card.
type MSAdaptiveCardAttachment struct {
ContentType string `json:"contentType"`
ContentURL *string `json:"contentUrl"`
Content MSAdaptiveCardContent `json:"content"`
}

// MSAdaptiveCardContent is the content payload for MS Adaptive Card.
type MSAdaptiveCardContent struct {
Schema string `json:"$schema"`
Type string `json:"type"`
Version string `json:"version"`
Body []MSAdaptiveCardBodyElement `json:"body"`
}

// MSAdaptiveCardBodyElement is the body element payload for MS Adaptive Card.
type MSAdaptiveCardBodyElement struct {
Type string `json:"type"`

*MSAdaptiveCardContainer `json:",inline"`
*MSAdaptiveCardTextBlock `json:",inline"`
*MSAdaptiveCardFactSet `json:",inline"`
}

// MSAdaptiveCardContainer is the container body element payload for MS Adaptive Card.
type MSAdaptiveCardContainer struct {
Items []MSAdaptiveCardBodyElement `json:"items,omitempty"`
}

// MSAdaptiveCardTextBlock is the text block body element payload for MS Adaptive Card.
type MSAdaptiveCardTextBlock struct {
Text string `json:"text,omitempty"`
Size string `json:"size,omitempty"`
Style string `json:"style,omitempty"`
Color string `json:"color,omitempty"`
Wrap bool `json:"wrap,omitempty"`
}

// MSAdaptiveCardFactSet is the fact set body element payload for MS Adaptive Card.
type MSAdaptiveCardFactSet struct {
Facts []MSAdaptiveCardFact `json:"facts,omitempty"`
}

// MSAdaptiveCardFact is the fact body element payload for MS Adaptive Card.
type MSAdaptiveCardFact struct {
Title string `json:"title"`
Value string `json:"value"`
}

// NewMSAdaptiveCard validates the MS Adaptive Card endpoint URL and returns an MSAdaptiveCard object.
func NewMSAdaptiveCard(hookURL string, proxyURL string, certPool *x509.CertPool) (*MSAdapativeCard, error) {
_, err := url.ParseRequestURI(hookURL)
if err != nil {
return nil, fmt.Errorf("invalid MS Adaptive Card endpoint URL %s: '%w'", hookURL, err)
}

return &MSAdapativeCard{
URL: hookURL,
ProxyURL: proxyURL,
CertPool: certPool,
}, nil
}

// Post sends the MS Adaptive Card message.
func (m *MSAdapativeCard) Post(ctx context.Context, event eventv1.Event) error {
// Skip Git commit status update event.
if event.HasMetadata(eventv1.MetaCommitStatusKey, eventv1.MetaCommitStatusUpdateValue) {
return nil
}

objName := fmt.Sprintf("%s/%s.%s", strings.ToLower(event.InvolvedObject.Kind), event.InvolvedObject.Name, event.InvolvedObject.Namespace)

message := &MSAdaptiveCardTextBlock{
Text: event.Message,
Wrap: true,
}
if event.Severity == eventv1.EventSeverityError {
message.Color = "attention"
}

facts := make([]MSAdaptiveCardFact, 0, len(event.Metadata))
for k, v := range event.Metadata {
facts = append(facts, MSAdaptiveCardFact{
Title: k,
Value: v,
})
}

// the card below was built with help from https://adaptivecards.io/designer using the Microsoft Teams host app
payload := &MSAdaptiveCardMessage{
Type: "message",
Attachments: []MSAdaptiveCardAttachment{
{
ContentType: "application/vnd.microsoft.card.adaptive",
ContentURL: nil, // should always be nil according to the docs referenced on the top of the file
Content: MSAdaptiveCardContent{
Schema: "http://adaptivecards.io/schemas/adaptive-card.json",
Type: "AdaptiveCard",
Version: "1.5",
Body: []MSAdaptiveCardBodyElement{
{
Type: "Container",
MSAdaptiveCardContainer: &MSAdaptiveCardContainer{
Items: []MSAdaptiveCardBodyElement{
{
Type: "TextBlock",
MSAdaptiveCardTextBlock: &MSAdaptiveCardTextBlock{
Text: objName,
Size: "medium",
Style: "heading",
Wrap: true,
},
},
{
Type: "TextBlock",
MSAdaptiveCardTextBlock: message,
},
{
Type: "FactSet",
MSAdaptiveCardFactSet: &MSAdaptiveCardFactSet{
Facts: facts,
},
},
},
},
},
},
},
},
},
}

err := postMessage(ctx, m.URL, m.ProxyURL, m.CertPool, payload)
if err != nil {
return fmt.Errorf("postMessage failed: %w", err)
}

return nil
}
5 changes: 5 additions & 0 deletions internal/notifier/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ var (
apiv1.BitbucketServerProvider: bitbucketServerNotifierFunc,
apiv1.BitbucketProvider: bitbucketNotifierFunc,
apiv1.AzureDevOpsProvider: azureDevOpsNotifierFunc,
apiv1.MSAdaptiveCardProvider: msadaptivecardNotifierFunc,
}
)

Expand Down Expand Up @@ -243,3 +244,7 @@ func bitbucketNotifierFunc(opts notifierOptions) (Interface, error) {
func azureDevOpsNotifierFunc(opts notifierOptions) (Interface, error) {
return NewAzureDevOps(opts.ProviderUID, opts.URL, opts.Token, opts.CertPool)
}

func msadaptivecardNotifierFunc(opts notifierOptions) (Interface, error) {
return NewMSAdaptiveCard(opts.URL, opts.ProxyURL, opts.CertPool)
}

0 comments on commit 534ee7e

Please sign in to comment.