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

Switch from deprecated message-cards to adaptive-cards #1

Merged
merged 7 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
.bitrise*
.gows.user.yml
.bitrise*
33 changes: 0 additions & 33 deletions Gopkg.lock

This file was deleted.

37 changes: 0 additions & 37 deletions Gopkg.toml

This file was deleted.

5 changes: 2 additions & 3 deletions bitrise.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ app:
# An example secret param, define it (MS_TEAMS_WEBHOOK_URL) in .bitrise.secrets.yml
- MS_TEAMS_WEBHOOK_URL: $MS_TEAMS_WEBHOOK_URL
# If you want to share this step into a StepLib
- BITRISE_STEP_ID: send-microsoft-teams-message
- BITRISE_STEP_ID: microsoft-teams-adaptive-card
- BITRISE_STEP_VERSION: "0.1.0"
- BITRISE_STEP_GIT_CLONE_URL: https://github.com/maguhiro/bitrise-step-send-microsoft-teams-message.git
- MY_STEPLIB_REPO_FORK_GIT_URL: https://github.com/maguhiro/bitrise-steplib
- BITRISE_STEP_GIT_CLONE_URL: https://github.com/HUK-COBURG/bitrise-step-microsoft-teams-adaptive-card.git

workflows:
success-test:
Expand Down
55 changes: 55 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
This file is:

The MIT License (MIT)

# Copyright (c) 2014 Bitrise

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package main

import (
"github.com/bitrise-io/go-steputils/stepconf"
"os"
)

// Buildstatus
var success = os.Getenv("BITRISE_BUILD_STATUS") == "0"

// Config ...
type Config struct {
// Settings
Debug bool `env:"is_debug_mode,opt[yes,no]"`
WebhookURL stepconf.Secret `env:"webhook_url"`
// Message Main
ThemeColor string `env:"theme_color"`
ThemeColorOnError string `env:"theme_color_on_error"`
Title string `env:"title"`
TitleOnError string `env:"title_on_error"`
// Message Git
AuthorName string `env:"author_name"`
Subject string `env:"subject"`
// Message Content
Fields string `env:"fields"`
Images string `env:"images"`
ImagesOnError string `env:"images_on_error"`
Buttons string `env:"buttons"`
ButtonsOnError string `env:"buttons_on_error"`
}
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module bitrise-step-microsoft-teams-adaptive-card

go 1.23.4

require (
github.com/atc0005/go-teams-notify/v2 v2.13.0
github.com/bitrise-io/go-steputils v0.0.0-20201016102104-03ae3a6ded35
github.com/bitrise-io/go-utils v0.0.0-20201211082830-859032e9adf0
)
182 changes: 130 additions & 52 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ This file is:

The MIT License (MIT)

Copyright (c) 2014 Bitrise
# Copyright (c) 2014 Bitrise

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -29,38 +29,16 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"os"
"strings"

"github.com/bitrise-io/go-steputils/stepconf"
"github.com/bitrise-io/go-utils/log"
"github.com/bitrise-tools/go-steputils/stepconf"
)

// Config ...
type Config struct {
// Settings
Debug bool `env:"is_debug_mode,opt[yes,no]"`
WebhookURL stepconf.Secret `env:"webhook_url"`
// Message Main
ThemeColor string `env:"theme_color"`
ThemeColorOnError string `env:"theme_color_on_error"`
Title string `env:"title"`
TitleOnError string `env:"title_on_error"`
// Message Git
AuthorName string `env:"author_name"`
Subject string `env:"subject"`
// Message Content
Fields string `env:"fields"`
Images string `env:"images"`
ImagesOnError string `env:"images_on_error"`
Buttons string `env:"buttons"`
ButtonsOnError string `env:"buttons_on_error"`
}

// success is true if the build is successful, false otherwise.
var success = os.Getenv("BITRISE_BUILD_STATUS") == "0"
"github.com/atc0005/go-teams-notify/v2/adaptivecard"
)

// selectValue chooses the right value based on the result of the build.
func selectValue(ifSuccess, ifFailed string) string {
Expand All @@ -70,32 +48,132 @@ func selectValue(ifSuccess, ifFailed string) string {
return ifFailed
}

// ensureNewlines replaces all \n substrings with newline characters.
func ensureNewlines(s string) string {
return strings.Replace(s, "\\n", "\n", -1)
func NewCard(c Config) adaptivecard.Card {

card := adaptivecard.NewCard()
card.Type = "AdaptiveCard"
card.Schema = "http://adaptivecards.io/schemas/adaptive-card.json"
card.Version = "1.5"

// Create style depending on build status
statusBanner := adaptivecard.NewContainer()
headline := adaptivecard.NewTextBlock("", false)
headline.Size = "Large"
headline.Weight = "Bolder"
headline.Style = "heading"
if success {
statusBanner.Style = "good"
headline.Color = "good"
headline.Text = "Success"
} else {
statusBanner.Style = "attention"
headline.Color = "Attention"
headline.Text = "Failed"
}
statusBanner.Spacing = "None"
statusBanner.Separator = true
statusBanner.Items = append(statusBanner.Items, headline)
card.Body = append(card.Body, adaptivecard.Element(statusBanner))

// Main Section
mainContainer := adaptivecard.NewContainer()
mainContainer.Style = "default"
mainContainer.Spacing = "Medium"
if selectValue(c.Title, c.TitleOnError) != "" {
mainContainer.Items = append(mainContainer.Items, adaptivecard.NewTextBlock(selectValue(c.Title, c.TitleOnError), false))
}

if c.AuthorName != "" {
mainContainer.Items = append(mainContainer.Items, adaptivecard.NewTextBlock(c.AuthorName, false))
}

if c.Subject != "" {
mainContainer.Items = append(mainContainer.Items, adaptivecard.NewTextBlock(c.Subject, true))
}

factSet := adaptivecard.NewFactSet()
for _, fact := range parsesFacts(c.Fields) {
err := factSet.AddFact(fact)
if err != nil {
log.Errorf("Could not add fact to factset %v", err)
}
}
if len(factSet.Facts) > 0 {
mainContainer.Items = append(mainContainer.Items, adaptivecard.Element(factSet))
}

if len(mainContainer.Items) > 0 {
card.Body = append(card.Body, adaptivecard.Element(mainContainer))
}

// Images
imageContainer := parsesImages(selectValue(c.Images, c.ImagesOnError))

if len(imageContainer.Items) > 0 {
card.Body = append(card.Body, adaptivecard.Element(imageContainer))
}

// Actions
actions := parsesActions(selectValue(c.Buttons, c.ButtonsOnError))
if len(actions.Actions) > 0 {
card.Body = append(card.Body, actions)
}

return card
}

func parsesFacts(s string) (fs []adaptivecard.Fact) {
for _, p := range pairs(s) {
fs = append(fs, adaptivecard.Fact{Title: p[0], Value: p[1]})
}
return
}

func parsesImages(s string) (container adaptivecard.Container) {
container = adaptivecard.NewContainer()

for _, p := range pairs(s) {

image := adaptivecard.Element{
URL: p[1],
Type: "Image",
Style: "default",
Size: "Small",
}

err := container.AddElement(false, image)
if err != nil {
log.Errorf("Could not add image %v", err)
}
}
return container
}

func parsesActions(s string) (as adaptivecard.Element) {
as = adaptivecard.NewActionSet()
for _, p := range pairs(s) {
action, _ := adaptivecard.NewActionOpenURL(p[1], p[0])
as.Actions = append(as.Actions, action)
}

return as
}

func newMessage(c Config) Message {
msg := Message{
Context: "https://schema.org/extension",
Type: "MessageCard",
ThemeColor: selectValue(c.ThemeColor, c.ThemeColorOnError),
Title: selectValue(c.Title, c.TitleOnError),
Summary: "Result of Bitrise",
Sections: []Section{{
ActivityTitle: c.AuthorName,
ActivityText: ensureNewlines(c.Subject),
Facts: parsesFacts(c.Fields),
Images: parsesImages(selectValue(c.Images, c.ImagesOnError)),
Actions: parsesActions(selectValue(c.Buttons, c.ButtonsOnError)),
}},
}

return msg
// pairs slices every lines in s into two substrings separated by the first pipe
// character and returns a slice of those pairs.
func pairs(s string) [][2]string {
var ps [][2]string
for _, line := range strings.Split(s, "\n") {
a := strings.SplitN(line, "|", 2)
if len(a) == 2 && a[0] != "" && a[1] != "" {
ps = append(ps, [2]string{a[0], a[1]})
}
}
return ps
}

// postMessage sends a message.
func postMessage(conf Config, msg Message) error {
// PostCard sends the given adaptive card to configured webhook
func PostCard(conf Config, msg adaptivecard.Card) error {
b, err := json.Marshal(msg)
if err != nil {
return err
Expand All @@ -118,7 +196,7 @@ func postMessage(conf Config, msg Message) error {
}()

if resp.StatusCode != http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("server error: %s, failed to read response: %s", resp.Status, err)
}
Expand All @@ -137,8 +215,8 @@ func main() {
stepconf.Print(conf)
log.SetEnableDebugLog(conf.Debug)

msg := newMessage(conf)
if err := postMessage(conf, msg); err != nil {
msg := NewCard(conf)
if err := PostCard(conf, msg); err != nil {
log.Errorf("Error: %s", err)
os.Exit(1)
}
Expand Down
Loading